此题解无病呻吟,啰里啰嗦,现已加入零分作文全家桶
这题......坑死我了......
不妨记原串长为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]!='