dfs序最优性的证明真是曲折呢

给你一些字符串,让你对其进行排列,使得按以下规则花费最少(然而题意真的不清楚,很容易就让人以为字符串的顺序是排好的)

($x$为字符串在自行排定的序列中的位置,当前字符串为$a$)
$1.$如果$a$存在后缀且a的后缀在a之后,花费$+=n^2$
$2.$如果$a$不存在后缀则花费$+=x$
$3.$设$y$为$a$之前离其最近的是$a$的后缀的字符串的位置,$a$存在后缀且$a$的后缀在$a$之前,则花费$+=x-y$

经过转化题意就比较显然了,但是我们如果仔细读题我们发现$1,2$规则其实完全没有必要。

规则1我们只要把$a$的后缀放在$a$之前就好了,这样肯定优于放在$a$的后面因为$x-y$的值最大只能是n-1显然优于$n^2$

规则2我们发现其实就是规则三中$y=0$的特殊情况,直接当规则3来处理就好

看到后缀我们显然首先想到的是后缀数组,但是本蒟蒻太弱了不会怎么办???(而且本蒟蒻也不知道本题能不能用后缀数组

我们把字符串翻个顺序就会发现后缀其实就是前缀,所以我们可以翻转字符串,处理前缀。

而处理前缀我们首先就会想到$trie$字典树,所以本题我们采用建立$trie$字典树的做法。

看懂题目的规则之后我们发现可以贪心的求解此题,只要让字符串的后缀与字符串之间所隔的字符串数目最小即可

所以第一步我们建立一颗$trie$树

第二步我们发现一个字符串有可能有很多后缀,所以我们需要判重,这里使用并查集

由于字符串的后缀与字符串之间存在有向的关系,便建立一张有向图。$x->y$代表$x$是$y$的后缀

因为要保证字符串与字符串的后缀之间距离最小所以贪心的选取以x为根的最小的子树

(使用vector在算出每棵子树大小后进行排序即可)
最后按照规则求和


如果你有认真看我的或其他人的题解,你可以发现我们在最后求和的时候是按照dfs序的。那么为什么?(我不会

所以我在夏令营时找了wqy神仙请教。

考虑建出一棵树之后,对于构建的任意合法的序列,都满足$i$的父亲一定在$i$之前出现,$i$的孩子们都一定在$i$之后出现

先尝试证明$dfs$序一定最优,对于以同一深度的节点为根的子树,在一颗中找一个叶子节点$j$,在另一颗里找一个根节点$i$(当前序列中$j$所在的子树的根在$i$之前出现

假设$j<i$的时候代价最小,尝试将$j$放在$i$的后面,此时$i<j$。那么我们发现如果$j$放在$i$与$i$的孩子们之间,$i$的孩子们和$i$的距离都会加$1$,所以总代价会增加,而$j$和$j$的父亲的距离一定也会增加,所以总代价也会增加

所以对于$j$所在子树的根比$i$在序列中出现的早的情况,$j<i$的情况是最优的

那么每个子树形成了一段连续的区间,对于每个节点进行调整,最后我们发现形成了一个$dfs$序(有时别的序列也会和$dfs$序列一样优秀,但是此处证明的是$dfs$序列是最优解之一,不是唯一解

接下来再证明先遍历子树小的更优秀。

$WQY$:
月明风清 $21:28:04$
因为你所有的$size$排序之后

月明风清 $21:28:17$
第$i$个根节点的贡献是前面所有树的$size$之和
所以要排序

综上所述,当我们进行$dfs$序且按子树大小排序时,求出代价即为最优。

$Q.E.D$

感谢$wqy$,给了我很大的帮助wucstdio

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
#define N 510050
#define M 100050
#define ll long long
ll ans=0;
char x[N];
int n,cnt,bo[N],tot=1,trie[5000050][27],fa[N],id[N],son[N],num;
vector<int>tu[N];
int read()
{
    int s=0,p=1;
    char ch=getchar();
    while(ch<'0' || ch>'9')
    {
        if(ch=='-')p=-1;
        ch=getchar();
    }
    while(ch>='0' && ch<='9')
    {
        s=(s<<1)+(s<<3)+(ch^48);
        ch=getchar();
    }
    return s*p;
}
int find(int x)
{
    if(fa[x]==x)return fa[x];
    else return fa[x]=find(fa[x]);
}
void insert(char *s,int bh)
{
    int l=strlen(s);
    int u=1;
    for(int i=l-1;i>=0;i--)
    {
        int c=s[i]-'a';
        if(!trie[u][c])trie[u][c]=++tot;
        u=trie[u][c];
    }
    bo[u]=bh;
}
void make(int x)
{
    for(int i=0;i<26;i++)
    {
        int v=trie[x][i];
        if(v)
        {
            if(!bo[v])
            {
                fa[v]=find(x);
            }
            else
            {
                tu[bo[find(x)]].push_back(bo[v]);
            }
            make(v);
        }
    }
}
int cmp(int x,int y)
{
    return son[x]<son[y];
}
void sonsum(int x)
{
    son[x]=1;
    for(vector<int> :: iterator it=tu[x].begin();it!=tu[x].end();it++)
    {
        int v=*it;
        sonsum(v);
        son[x]+=son[v];
    }
    sort(tu[x].begin(),tu[x].end(),cmp);
}
void dfs(int x)
{
    id[x]=num++;
    for(vector<int> :: iterator it=tu[x].begin();it!=tu[x].end();it++)
    {
        int v=*it;
        ans+=num-id[x];
        dfs(v);
    }
}
void dfss(int x)
{
    for(vector<int> :: iterator it=tu[x].begin();it!=tu[x].end();it++)
    {
        int v=*it;
        cout<<v<<endl;
        dfss(v);
    }
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        scanf("%s",x);
        insert(x,i);
    }
    for(int i=1;i<=tot;i++)fa[i]=i;
    make(1); sonsum(0);dfs(0);
    printf("%lld",ans);
    return 0;
}

并查集去重的思路来源于此篇blog

爱我所爱,行我所行,听从我心,无问西东。