[SCOI2016]背单词

题目

题目

做法

我们把每个单词反过来,然后如果\(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\)

总代价就是\(\sum\limits_{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\n",ans);
	return  0;
}
posted @ 2020-10-26 16:49  敌敌畏58  阅读(82)  评论(0编辑  收藏  举报