LA 3026 / HDU 1358 Period (kmp,证明的应该挺详细)

本文深入探讨了KMP算法中next数组的性质及其在识别字符串循环节的应用。通过构造串的KMP数组,next[i]表示0~i-1子串前缀和后缀集合最大相同长度,证明了当i%(i-next[i])==0时,0~i-1是由循环节组成的前缀。代码示例展示了如何实现KMP算法并识别循环节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接: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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值