题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3746
解题思路:
结论:最小循环节长度为len-fail[len]
len表示字符串长度,字符串的下标0~len-1,fail[len]表示0~len-1的最长相同前后缀
①对于2*fail[len]<=len的情况显然成立,且要补足的为len-fail[n]
②如果大于2*fail[len]>len
问题:怎么证明这是一个循环节
看图:
橙色条为相同前后缀,上者 绿+粉 = 下者 粉+蓝
现在需证明:蓝色块=绿色块,这样便可以得到 所需添加字符为粉色快
看图可知上面绿色块和下面蓝色块都是橙色条最后相同的一部分。
③特殊情况,如果本身就循环,那么循环节长度为len-fail[n](但要补足的为0)
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5+5;
char s[N];
int fail[N];
void kmp(int len)
{
fail[0] = -1;
for (int i=0,j=-1;i<len;){
if (j==-1||s[i]==s[j]){
i++;
j++;
fail[i] = j;
}
else j = fail[j];
}//printf("fail[len]=%d\n",fail[len]);
int cnt = len - fail[len];
if (fail[len] && len%cnt==0) printf("0\n");
else {
printf("%d\n",cnt-len%cnt);
}
}
int main()
{
int T;
scanf("%d",&T);
while (T--){
scanf("%s",s);
int len = strlen(s);
kmp(len);
}
return 0;
}
正文到上面就结束了。
下面是第一次写题时候的题解,比较麻烦,没有闲情雅致不用看了吧 o(* ̄︶ ̄*)o。
题目意思是在这个串后补最少的字符使其变成循环的串。
我们知道利用KMP可以求出最长前后缀大小
先放图
最上,绿色表示相同前后缀大小,显然,此时只需要加粉色快大小在末端就循环了。
所以当前后缀没有重叠就可以直接得出答案。
而第三个图,我们发现最长前后缀是 (绿+粉+绿),也就是重叠了,这个时候要怎么考虑呢,把前缀或者后缀抠出来,重叠部分(一个小绿块)就是这个前缀或者后缀中的最长前后缀。
然后也会有抠出前缀,结果前缀的前缀和后缀的前缀又重叠,所以需要不停向下分解直到不重叠。最终随着重叠部分(就是分解出前缀的最短前缀)越来越短一定会不重叠的。
你要证明这个不停分解最终求出答案过程是否对的话从最终状态往上推就行了。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5+5;
char s[N];
int fail[N];
void kmp(int len)
{
fail[0] = -1;
for (int i=0,j=-1;i<len;){
if (j==-1||s[i]==s[j]){
i++;
j++;
fail[i] = j;
}
else j = fail[j];
}
//for (int i=0;i<=len;i++) printf("fail[%d]=%d\n",i,fail[i]);
int j = fail[len];
if (j && len%(len-j)==0){
printf("0\n");
return ;
}
if (j*2<=len){
printf("%d\n",len-j*2);
return ;
}
while (1){
if (fail[j]*2<=j){
printf("%d\n",j-fail[j]*2);
return ;
}
j = fail[j];
}
}
int main()
{
int T;
scanf("%d",&T);
while (T--){
scanf("%s",s);
kmp(strlen(s));
}
return 0;
}
雨女无瓜的自我总结:
1.KMP可以用于判断当前串是否是循环串,还可以添加最少字符使其变成循环串
2.在思考怎么证明的时候一直在思考怎么样从顶分解到底,结果发现从底到顶比较容易想。
3.判断是否循环除了len%(len-fail[len])==0,还有个前提是fail[len]!=0,千万别忘了呀朋友