题目
做法
我们把每个单词反过来,然后如果(st[i])是(st[j])的前缀,且不存在(st[k])是(st[j])的前缀,且(st[i])是(st[k])的前缀,那么(i)是(j)的父亲,显然这样构造不存在环,且是一个森林。(构造方法:字典树)
不难发现,如果你每个点都等着祖先拿完再拿(拿就是背下来的意思),那么价值都是≤(n)的,总的价值小于等于(n^2),也就是说第一条规则就是废的,一个点一定得在祖先拿完之后再拿。
好,假设现在构造出一种拿点方案:(a_1,a_2,a_3,...,a_n),(b)数组的定义是:如果(a_i=j),那么(b_j=i)。
总代价就是(sumlimits_{i=1}^{n}b[i]-b[fa[i]])。(当然,对于一个点没有父亲就设其父亲为(0),(b[0])永远为(0))
考虑通过调换位置让这个更加优秀,对于(x)而言((x)需要满足在(a)数组中其的任意一个子树节点到(x)的这么一段区间中不存在一个不是(x)子树的点,即(x)的子树是连续的一段,这样有什么好处,我们移动(x)子树这么连续的一段时,值会改变的只有(b[x]-b[fa[x]])),如果其到(fa[x])的这一段方案中,如果存在(y)点不在(fa[x])的子树当中((y)也需要满足跟(x)同样的要求,且(b[fa[y]]>b[fa[x]]),假设存在(y)不在(fa[x])的子树,那么一定存在满足要求的(y))
这样的话,我们只需要把(y)连通其子树放到(fa[x])前面即可,这样增加量为:(-(b[y]-b[fa[x]])+size[y]-size[y]=-(b[y]-b[fa[x]]))(分别为(y,fa[x],x)的减少量),而这个是严格小于(0)的,即可以让结果更小,我们称此为一次操作。
好,那么为什么假设存在(y)不在(fa[x])的子树,那么一定存在满足要求的(y),考虑如果(y)不满足与(x)同样的要求,那么我们先去处理(y)的子树,那么新处理的点(z)((z)在(y)的子树且(z)的祖先是(y)),其(b[z]<b[fa[z]]<b[y]<b[fa[x]]),这样不断循环下去,(b[z])最终最多会等于(n),也就是一定会停下来,所以一定可以通过一定的操作数构造出(y)与(x)同样的要求。
那么如果(b[fa[y]]>b[fa[x]])呢?
那么就把(y)设为(fa[y]),然后重复同样的步骤。
这样,我们最终构造出来的方案是什么呢?
不难发现,其实就是一个(DFS)序,即一个点被拿之后直接拿完其子树是最优秀的。
因此可以直接(DFS)走一遍。
怎样的(dfs)序是最快的呢?
不难发现,每个点的权值只与其父亲有关,所以(x)遍历儿子的顺序会改变每个儿子贡献的权值。
设儿子序列为(a_1,a_2,a_3,...,a_k)。
那么答案就为:(1+(1+size[a_1])+(1+size[a_1]+size[a_2])...)
考虑如果(size[a_{i}]>size[a_{i+1}])的话,交换(a_{i},a_{i+1})会有什么影响?不难发现,其余儿子的贡献不变,只有(a_i,a_{i+1})变了,增加量为(size[a_{i+1}]-size[a_i]),而这个是小于(0)的,也就是更加的优秀,然后结合冒泡排序的思想,不难发现,优先遍历(size)小的儿子是最优秀的。
时间复杂度:(O(nlogn+|len|))
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 510000
using namespace std;
typedef long long LL;
struct TREE
{
int y,next;
}a[N];int len,last[N];
inline void ins(int x,int y){len++;a[len].y=y;a[len].next=last[x];last[x]=len;}
struct node
{
int v,son[30];
}tr[N];int cnt=1,rt=1/*根*/;
inline int add(char *s,int id)
{
int now=1;
for(int i=strlen(s)-1;i>=0;i--)
{
int x=s[i]-'a';
if(!tr[now].son[x])tr[now].son[x]=++cnt;
now=tr[now].son[x];
}
tr[now].v=id;
return now;
}
void dfs1(int x,int pre)
{
if(tr[x].v)ins(pre,tr[x].v);
for(int i=0;i<26;i++)
{
if(tr[x].son[i])dfs1(tr[x].son[i],tr[x].v?tr[x].v:pre);
}
}
int siz[N];
LL ans;
int list[N],top;
inline bool cmp(int x,int y){return siz[x]<siz[y];}
void dfs2(int x)
{
siz[x]=1;
for(int k=last[x];k;k=a[k].next)
{
int y=a[k].y;
dfs2(y);siz[x]+=siz[y];
}
top=0;
for(int k=last[x];k;k=a[k].next)list[++top]=a[k].y;
if(top)
{
sort(list+1,list+top+1,cmp);
LL sum=1;
for(int i=1;i<=top;i++)ans+=sum,sum+=siz[list[i]];
}
}
int n;char st[N];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%s",st);
add(st,i);
}
dfs1(1,0);
dfs2(0);
printf("%lld
",ans);
return 0;
}