题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1358
参考资料:算法竞赛入门经典 训练指南,刘汝佳 陈锋 著
解题思路:
构造串的kmp数组,next[i]表示0~i-1子串前缀和后缀集合最大相同长度。
例子:abbabb(编号0~5)
前缀集合:a,ab,abb,abba,abbab
后缀集合:b,bb,abb,babb,bbabb
最长为abb,所以next[6] = 3
那么这个性质和这题有什么关系呢?
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
①若0~i-1是由n个最短循环节构成的,那么next[i]的值就是n-1个循环节的长度。(证明P->Q成立)
我们心里肯定能想到至少等于n-1个循环节长度吧,那么你会不跟我一样想:有没有那么一个特殊的循环节,可以使得next[i]比
n-1个循环节再大一点导致这个结论不成立呢。
于是我们来证明不可能发生这种状况:
假设next[i] = n-1个循环节+这一坨黄的
假设这一部分黄的的值用a1~ax代替(简称1~x)
最长后缀前面的黄的和最长前缀前面的绿的比较,那么一个循环节开头也是1~x
这个1~x可以得出一个循环节第x+1位~2x位也是1~x(两个串比较,每个位置都要相同,这是我们假设的)
然后不停递推,可以把整个循环节的值都用1~x代替
如果恰好填满,那么循环节将不是这个坨绿的,而是这坨黄的。
如果有部分重叠,又想保证 ?~x ==1~??的字符就要在这个1~x的黄块里面递推。最后一定可以搞出循环节比绿的小(最不济每个字符都一样循环节为1这时候一定满足了)。
所以 由 如果成立-->绿的不是最小循环节 来证明 P->Q正确
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
②如果i%(i-next[i])==0,证明0~i-1是一个由循环节组成的前缀(有 i/(i-next[i])个循环节)(证明Q->P成立)
我们要用到的是这个,也就是Q->P。
那我前面证明①结论干什么呢???
不管了,水水的证明一下②吧。
按照证明①时的那种递推思路即可。1->2->3->4->5->6->7
我们只需要证明:绿色长度/黄色长度 = 整数
假设绿色长度为x,那么黄色为n-x
假设 n/(n-x)=z (z代表整数)
那么绿色 x = n-n/z
黄色 n-x = n/z
相除约分得到 (zn-n)/n,再约分=z-1,是整数
所以②证明完毕
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
代码:
#include<cstdio>
const int N = 1e6+5;
char s[N];
int nt[N];
void get_next(int len)
{
nt[0] = -1;
for (int i=0,j=-1;i<len;){
if (j==-1||s[i]==s[j]){
i++;
j++;
nt[i] = j;
}
else j = nt[j];
}
//for (int i=0;i<=len;i++) printf("next[%d]=%d\n",i,nt[i]);
}
int main()
{
int n;
for (int sb=1;scanf("%d",&n),n;sb++){
scanf("%s",s);
get_next(n);
printf("Test case #%d\n",sb);
for (int i = 2;i<=n;i++){
int l = i-nt[i];
if (nt[i] && i%l==0) printf("%d %d\n",i,i/l);///如果nt[i]=0表示字符 0 ~ i-1 无公共前后缀
}
printf("\n");
}
return 0;
}