題目傳送node
閱讀理解題題意解釋能夠看這位大佬的博客。ios
發現求後綴與倒序求前綴是等價的,而找前綴天然就想到了trie樹。將全部字符串翻轉後再建入trie樹中,再對每個字符串翻轉後從trie樹中找前綴,就能找到一個字符串的全部後綴了。git
由第三種狀況知咱們要想最小化總代價,則最小化一個字符串與最靠近它的後綴間的距離應該也是一個要考慮的因素。其實一個串最近的後綴其實必定是它的全部後綴中長度最大的,由於它的後綴中長度短的也必定是長度大的後綴,而且還要避免第一種狀況的出現(即序列在一個字符串後面的串中不能有這個字符串的後綴)。故咱們只要知道每一個字符串最長的後綴就好。若一個串是另外一個串的後綴,則能夠從這個串向另外一個串連一條有向邊,則最終會造成一個森林。ide
這時避免了第一種狀況後,還有兩種狀況。咱們作了這麼多題,知道一種狀況總應該比多種狀況好作。故考慮將兩種狀況轉化爲一種。發現若是咱們在全部題目給出的字符串的基礎上再加入一個處於序列第0個位置的空串,那麼第二種狀況也就轉化爲了第三種狀況,而且對答案沒有影響。這種轉化對於當前的森林,只要再建個標號爲0的空串節點連向全部樹的根就行了。優化
此時問題就轉化爲:給樹的節點編號,要求父親節點的序號比兒子節點小,且根節點的序號爲0,並使得兒子節點的編號減父親節點編號的差的和最小,求最小的和。考慮怎麼選點編號。做者一開始也懵的一逼因而看了看這位大佬博客關於選法的證實部分(「考慮建出……Q.E.D」)終於明白若是選到了一個點,就要一口氣把它的子樹都選了,而且優先選子樹大小最小的兒子。dfs就搞定了呀。至於對某個點的兒子從子樹大小小的往大的選,能夠用堆維護。spa
具體實現請看代碼:3d
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<queue> 5 6 #define max(a,b) ((a)>(b)?(a):(b)) 7 8 using namespace std; 9 10 const int N=100005,LEN=510005; 11 12 int tree[510005][26],cnt,n,l,dfs; 13 int ed[LEN],in[N],lst[N],to[N],nxt[N],ecnt,dfn[N]; 14 15 long long siz[N],ans; 16 17 string word[N]; 18 19 char ch; 20 21 inline int read() 22 { 23 int x=0; 24 ch=getchar(); 25 while(!isdigit(ch)) ch=getchar(); 26 while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar(); 27 return x; 28 } 29 30 inline void getstring(string &a) 31 { 32 a=""; 33 ch=getchar(); 34 while(ch<'a'||ch>'z') 35 ch=getchar(); 36 while(ch>='a'&&ch<='z') 37 a+=ch,ch=getchar(); 38 } 39 40 inline void insert(const string &a,int j) 41 { 42 l=a.length(); 43 int now=0,num; 44 for(int i=l-1;i>=0;--i)//要將字符串倒序插入trie樹中 45 { 46 num=a[i]-'a'; 47 if(!tree[now][num]) 48 tree[now][num]=++cnt; 49 now=tree[now][num]; 50 } 51 ed[now]=j; 52 } 53 54 inline void addedge(int u,int v) 55 { 56 nxt[++ecnt]=lst[u]; 57 lst[u]=ecnt; 58 to[ecnt]=v; 59 } 60 61 inline int fin(const string &a) 62 { 63 int now=0,num,ret=-1; 64 l=a.length(); 65 for(int i=l-1;i>=0;--i)//查後綴就是倒序查前綴 66 { 67 num=a[i]-'a'; 68 if(!tree[now][num]) 69 return ret; 70 now=tree[now][num]; 71 if(ed[now]&&i) 72 ret=ed[now]; 73 } 74 return ret; 75 } 76 77 void dfssiz(int u) 78 { 79 siz[u]=1; 80 for(int e=lst[u];e;e=nxt[e]) 81 { 82 dfssiz(to[e]); 83 siz[u]+=siz[to[e]]; 84 } 85 } 86 87 struct node{ 88 int lar,ord; 89 }head; 90 91 inline bool operator < (const node &a,const node &b) 92 { 93 return a.lar>b.lar;//大根堆變小根堆 94 } 95 96 void dfsans(int u) 97 { 98 priority_queue<node>hep; 99 for(int e=lst[u];e;e=nxt[e]) 100 { 101 hep.push((node){siz[to[e]],to[e]}); 102 } 103 while(!hep.empty()) 104 { 105 head=hep.top(); 106 hep.pop(); 107 dfn[head.ord]=++dfs;//編號過程 108 ans+=dfn[head.ord]-dfn[u]; 109 dfsans(head.ord); 110 } 111 } 112 113 int main() 114 { 115 n=read(); 116 for(int i=1;i<=n;++i) 117 { 118 getstring(word[i]);//字符串的讀入優化 119 insert(word[i],i); 120 } 121 int u; 122 for(int i=1;i<=n;++i) 123 { 124 u=fin(word[i]); 125 if(u!=-1) 126 { 127 addedge(u,i); 128 in[i]++; 129 } 130 } 131 for(int i=1;i<=n;++i) 132 if(!in[i])//沒有入度的點就是森林中樹的根 133 addedge(0,i); 134 dfssiz(0);//求一下每一個點的子樹大小。 135 dfsans(0);//統計答案。 136 printf("%lld",ans); 137 return 0; 138 }