1. 程式人生 > 實用技巧 >[SCOI2016]背單詞

[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;
}