此题解无病呻吟,啰里啰嗦,现已加入零分作文全家桶
这题......坑死我了......
不妨记原串长为i的前缀为prefix(i),next[i]表示prefix(i)的最长公共前后缀长度(不等于prefix(i)自身)(就是MP的next[])
首先,一个前缀必须同时也是原串的后缀才有可能成为答案,否则原串的最后一部分无法覆盖。
所以我们沿着next表往前走,可以找出原串的所有公共前后缀,筛选出可能的答案。
接下来我们验证每个可能的答案。如果用prefix(i)作为模板串,我们可以用KMP算法直接找出prefix(i)在原串中所有的出现位置,这些位置就是prefix(i)可以涂到的位置。接下来把能涂上模板的位置都涂上,判断能否覆盖整个字符串。这相当于找出相邻两个能够涂上模板的位置的最大间隔,看一下这个间隔长度和prefix(i)的长度哪一个大,如果最大间隔比prefix(i)长就说明不能覆盖。
这样暴力做是O(n^2)的。本脑子有坑选手考试连O(n^2)都没推出来,打的O(n^2logn)的->_->
接下来好多人都假定“prefix(i),prefix(j)是两个公共前后缀,i<j,如果prefix(i)是一个可行解,那么prefix(j)一定是可行解,如果prefix(j)不是可行解,那么prefix(i)一定不是可行解”,于是二分答案。但这个题其实没有单调性。比如这个数据:
aabccaabccaabcaabccaabcaabccaabccaabccaabccaabccaabcaabccaabc
prefix(9)是可行解,但prefix(23)不是可行解.....所以二分答案是错的,也可以构造数据卡掉,然而bzoj上好多二分答案过掉的->_->
考虑两个可能的答案prefix(j)和prefix(i),j<i.原串中的某个位置如果能够匹配上prefix(i),那么这个位置一定也能够匹配上prefix(j),但匹配上prefix(j)不一定能够匹配上prefix(i),这启发我们可以按照一定的顺序枚举可能的答案,同时维护所有相邻匹配点的最大间隔。既然题中求的是最小的长度,我们不妨从短到长枚举答案,此时匹配点不断减少。
问题变成:如何快速找出从较短答案变为一个较长答案时减少的匹配点,如何快速完成匹配点的删除并维护最大间隔。
第二个问题,我们可以用一个只带删除操作的链表维护所有匹配点。
这个链表由于只有删除,可以直接用数组实现,pre[i]表示如果点i是匹配点,前一个匹配点是谁;suc[i]表示如果点i是匹配点,下一个匹配点是谁。那么删除i的时候我们用(suc[i]-pre[i])更新最大间隔,并维护suc[]和pre[]数组即可。
第一个问题则需要用到fail树。对1<=i<=n,我们连一条从next[i]到i的边,根节点为0,形成一棵fail树,树中的每个节点i其实代表了原串的一个前缀prefix(i)。fail树中节点i到根节点的路径上包含了prefix(i)的所有公共前后缀。记fail树中以i为根的子树为subtree(i)
fail树的一个性质是:prefix(i)在原串中所有的出现位置(以prefix(i)出现时最后一个字符的下标表示出现位置)都在subtree(i)中
如果prefix(i)的一个出现位置是k,那么prefix(i)就是prefix(k)的后缀,也就是说prefix(i)是prefix(k)的公共前后缀,因此i在k到根节点的路径上,故k在subtree(i)中。
如果k在subtree(i)中,那么i就在k到根节点的路径上,因此prefix(i)是prefix(k)的公共前后缀,故prefix(i)的一个出现位置是k
然后我们发现,从短到长枚举所有可能答案的过程其实是在fail树上从树根朝着节点n走,当前验证节点i时所有可能的匹配点就是subtree(i)中的点,减少的匹配点一定不在subtree(i)中却在subtree(i的父亲)中。因此每走一步我们就用bfs找出subtree(i的父亲)中不属于subtree(i)的点,将它们从链表中删掉即可。
#include<cstdio> #include<cstring> const int maxn=500005; char str[maxn]; int next[maxn]; int getnext(){ int i=1,j=0;next[0]=next[1]=0; for(;str[i]!='\0';++i){ while(j&&str[i]!=str[j])j=next[j]; if(str[i]==str[j])++j; next[i+1]=j; } return i; } int pre[maxn],suc[maxn]; struct edge{ int to,next; }lst[maxn];int len=1; int first[maxn]; void addedge(int a,int b){ lst[len].to=b; lst[len].next=first[a]; first[a]=len++; } int maybe[maxn],tot=0; int maxgap=1; void del(int x){ if(suc[x])pre[suc[x]]=pre[x]; if(pre[x])suc[pre[x]]=suc[x]; if(pre[x]!=0&&suc[x]!=0&&suc[x]-pre[x]>maxgap)maxgap=suc[x]-pre[x]; pre[x]=suc[x]=0; }; int q[maxn],head,tail; void bfs(int s,int avoid){ head=tail=0; q[tail++]=s; while(head!=tail){ int x=q[head++]; if(x==avoid)continue; del(x); for(int pt=first[x];pt;pt=lst[pt].next){ q[tail++]=lst[pt].to; } } } int main(){ scanf("%s",str); int n=getnext(); for(int i=1;i<=n;++i){ addedge(next[i],i); } for(int i=n;i;i=next[i])maybe[++tot]=i; for(int i=1;i<=n;++i){ pre[i]=i-1; suc[i]=i+1; } suc[n]=0; for(int i=tot;i>=1;--i){ bfs(maybe[i+1],maybe[i]); if(maxgap<=maybe[i]){ printf("%d\n",maybe[i]); break; } } return 0; }