阅读理解题题意解释可以看这位大佬的博客。
发现求后缀与倒序求前缀是等价的,而找前缀自然就想到了trie树。将所有字符串翻转后再建入trie树中,再对每一个字符串翻转后从trie树中找前缀,就能找到一个字符串的所有后缀了。
由第三种情况知我们要想最小化总代价,则最小化一个字符串与最靠近它的后缀间的距离应该也是一个要考虑的因素。其实一个串最近的后缀其实一定是它的所有后缀中长度最大的,因为它的后缀中长度短的也一定是长度大的后缀,并且还要避免第一种情况的出现(即序列在一个字符串后面的串中不能有这个字符串的后缀)。故我们只要知道每个字符串最长的后缀就好。若一个串是另一个串的后缀,则可以从这个串向另一个串连一条有向边,则最终会形成一个森林。
这时避免了第一种情况后,还有两种情况。我们做了这么多题,知道一种情况总应该比多种情况好做。故考虑将两种情况转化为一种。发现如果我们在所有题目给出的字符串的基础上再加入一个处于序列第0个位置的空串,那么第二种情况也就转化为了第三种情况,并且对答案没有影响。这种转化对于当前的森林,只要再建个标号为0的空串节点连向所有树的根就好了。
此时问题就转化为:给树的节点编号,要求父亲节点的序号比儿子节点小,且根节点的序号为0,并使得儿子节点的编号减父亲节点编号的差的和最小,求最小的和。考虑怎么选点编号。作者一开始也懵的一逼于是看了看这位大佬博客关于选法的证明部分(“考虑建出……Q.E.D”)终于明白如果选到了一个点,就要一口气把它的子树都选了,并且优先选子树大小最小的儿子。dfs就搞定了呀。至于对某个点的儿子从子树大小小的往大的选,可以用堆维护。
具体实现请看代码:
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 }