KMP专题小结

关于KMP的资料自然是matrix67的最好了(ORZ),以及任何专题都可以套着看看的大白书(ORZ)~

http://www.matrix67.com/blog/archives/115

拓展KMP:还是刘雅琼的PPT讲得最清晰(ORZ)

http://wenku.baidu.com/view/8e9ebefb0242a8956bece4b3.html

接下来就是题集了

【HDU 1358】  period:这道题考察了KMP的基本应用

如果前面部分是一个循环,那么循环节的必然为i-next[i]

如果i%(i-next[i])==0则可以得到依次对应相等。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int SIZEN=1000005;
char str[SIZEN];
int next[SIZEN];
void getnext(char *s,int len){
    next[0]=next[1]=0;
    for(int i=1;i<len;i++){
        int j=next[i];
        while(j&&s[j]!=s[i]) j=next[j];
        next[i+1]=s[j]==s[i]?j+1:0;
    }
}
int main()
{
    int i,j;
    int n,m;
    int len,txt=1;
    while(scanf("%d",&len)!=EOF&&len){
        scanf("%s",str);
        getnext(str,len);
        printf("Test case #%d\n",txt++);
        for(i=1;i<=len;i++){
            if(next[i]&&i%(i-next[i])==0)
                printf("%d %d\n",i,i/(i-next[i]));
        }
        printf("\n");
    }
    return 0;
}
【HDU 1711】Number Sequence:

这道题是KMP的最基础的应用了。只是注意数有正负之分(一开始没注意想用输入外挂提速跪得意识模糊)

#include<cstdio>
#include<cstring>
const int SIZE=1000005;
int str1[SIZE],str2[10005];
int next[10005];
int len1,len2;
void getnext()
{
    next[0]=next[1]=0;
    for(int i=1;i<len2;i++)
    {
        int j=next[i];
        while(j&&str2[j]!=str2[i]) j=next[j];
        next[i+1]=str2[j]==str2[i]?j+1:0;
    }
}
int main()
{
    //freopen("data.in","r",stdin);
    int i,j,t,k;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d %d",&len1,&len2);
        for(i=0;i<len1;i++) scanf("%d",&str1[i]);
        for(i=0;i<len2;i++) scanf("%d",&str2[i]);
        getnext();
        j=k=0;
        for(i=0;i<len1;i++)
        {
            while(j&&str2[j]!=str1[i]) j=next[j];
            if(str2[j]==str1[i]) j++;
            if(j==len2) {k=i-j+2;break;}
        }
        if(k)  printf("%d\n",k);
         else printf("-1\n");
    }
    return 0;
}
【HDU 2203】亲和串:

这道题仅仅需要把主串复制一遍,然后用KMP扫一下就OK了

不过要注意模版串的长度大于主串长度的情况

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int SIZEN=300005;
char s1[SIZEN],s2[SIZEN];
int f[SIZEN];
void getfail(char *s,int len){
    f[0]=f[1]=0;
    for(int i=1;i<len;i++){
        int j=f[i];
        while(j&&s[j]!=s[i]) j=f[j];
        f[i+1]=s[j]==s[i]?j+1:0;
    }
}
int main()
{
    int i,j;
    int n,m;
    while(scanf("%s%s",s1,s2)!=EOF){
        int len1=strlen(s1),len2=strlen(s2);
        memcpy(s1+len1,s1,sizeof(char)*(len1+1));
        if(len1<len2){
            printf("no\n");
            continue;
        }
        len1*=2;
        getfail(s2,len2);
        int ans=0;
        int j=0;
        for(i=0;i<len1;i++){
            while(j&&s2[j]!=s1[i]) j=f[j];
            if(s2[j]==s1[i]) j++;
            if(j==len2) {ans=1;break;}
        }
        if(ans) printf("yes\n");
            else printf("no\n");
    }
    return 0;
}
【HDU 3336】 Count the string:

这道题处于一种数据奇弱的状态,而本弱之前用错误代码AC了之后再也没关注过他

后来在CF上做到一道类似题顿时傻眼,后来才听说这道题是一模一样的。

故重新研究了一下。

我们要求与前缀相同的子串的总和

就要求不同长度与前缀相同的子串的和

故设cnt[i]为与长度为i的前缀相同的子串的个数

KMP中的f数组的意思是f[i-next[i]...i-1]==f[0..next[i]-1]

因此可以得到cnt[next[i]]=cnt[next[i]]+cnt[i];

若i从大到小递推就可以得到字串的个数,因为若从小到大的话

在较长子串中会包含有较小字串,而由较小字串推更小子串的时候,较小子串是没有统计完全的

因此需要从大到小。而KMP中的f数组记录的是:与前缀相同的最长数列。故正确性得到了。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define MOD 10007
using namespace std;
const int SIZEN=200005;
char str[SIZEN];
int f[SIZEN];
int cnt[SIZEN];
void getfail(char s[],int len){
    f[0]=f[1]=0;
    for(int i=1;i<len;i++){
        int j=f[i];
        while(j&&s[j]!=s[i]) j=f[j];
        f[i+1]=s[j]==s[i]?j+1:0;
    }
}
int main()
{
    int i,j;
    int n,m,_;
    int len;
    scanf("%d",&_);
    while(_--){
        scanf("%d",&len);
        scanf("%s",str);
        getfail(str,len);
        memset(cnt,0,sizeof(cnt));
        for(i=len;i>0;i--) cnt[i]++;//前缀自身的串数
        for(i=len;i>0;i--) cnt[f[i]]=(cnt[f[i]]+cnt[i])%MOD;//统计每个长度子串的个数
        int ans=0;
        for(i=1;i<=len;i++) ans=(ans+cnt[i])%MOD;
        printf("%d\n",ans);
    }
    return 0;
}
codeforces #246 div2 PD与此题异曲同工,可以尝试一做

【HDU 3613】Best Reward

分析题意:通过讲字符串一分为二,得到一个以上的权值最大的回文子串

因为是一分为二,故回文子串只可能在头或尾产生,因此就想到了扩展KMP

EKMP中extend[i]的定义是:主串中以s[i]开头的子串与模式串前缀的最长长度

因此就想到:能不能将字符串反转,然后做两次EKMP,就可以判断了?

答案是肯定的。那再用前缀和记录前后的val,若为回文串就加上,扫一遍取最大值accepted get!

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int SIZEN=500005;
char str[SIZEN],sstr[SIZEN];
int next[SIZEN];
int extend1[SIZEN],extend2[SIZEN];
int val[SIZEN],len;
void EKMP(char s[],char t[],int extend[]){
    int i,j,p,l;
    next[0]=len;
    j=0;
    while(1+j<len&&t[j]==t[1+j]) j++;
    next[1]=j;
    int a=1;
    for(i=2;i<len;i++){
        p=next[a]+a-1;
        l=next[i-a];
        if(i+l<p+1) next[i]=l;
        else{
            j=max(0,p-i+1);
            while(i+j<len&&t[i+j]==t[0+j]) j++;
            next[i]=j;
            a=i;
        }
    }
    j=0;
    while(j<len&&j<len&&s[j]==t[j]) j++;
    extend[0]=j;
    a=0;
    for(i=1;i<len;i++){
        p=extend[a]+a-1;
        l=next[i-a];
        if(l+i<p+1) extend[i]=l;
        else{
            j=max(0,p-i+1);
            while(i+j<len&&j<len&&s[i+j]==t[j]) j++;
            extend[i]=j;
            a=i;
        }
    }
}
int main()
{
    //freopen("data.in","r",stdin);
    int i,j;
    int n,m,_;
    int v[100];
    scanf("%d",&_);
    while(_--){
        for(i=0;i<26;i++) scanf("%d",&v[i]);
        scanf("%s",str);
        len=strlen(str);
        for(i=0;i<len;i++)
            sstr[len-1-i]=str[i];
        sstr[len]='\0';
        EKMP(str,sstr,extend1);
        EKMP(sstr,str,extend2);//1是以原串为主串,2是以逆串为主串
        val[0]=v[str[0]-'a'];
        for(i=1;i<len;i++) val[i]=val[i-1]+v[str[i]-'a'];
        int ans=0,tans;
        for(i=1;i<len;i++)
        {
            tans=0;
            if(extend1[i]==len-i) tans+=val[len-1]-val[i-1];
            if(extend2[len-i]==i) tans+=val[i-1];
            ans=max(ans,tans);
        }
        printf("%d\n",ans);
    }
    return 0;
}
【HDU 3746】Cyclic Nacklace

题意是问你,最少加上多少个珠子可以使整个串有个循环

首先,既然是循环,那么循环节至少有两个

其次,再想想KMP中f数组的定义

就可以知道:若存在循环,则循环节长度必然是len-next[len]==round

若round==len,那么只有一个循环节,就应当补充len个

若l en%round==0 则存在两个以上循环节(1个已经被排除了),输出0

若都不满足,则不满足循环的串的长度为len%(round),需要补充的长度为round-len%(round)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int SIZEN=100005;
char str[SIZEN];
int f[SIZEN];
void getfail(char *s,int len){
    f[0]=f[1]=0;
    for(int i=1;i<len;i++){
        int j=f[i];
        while(j&&s[i]!=s[j]) j=f[j];
        f[i+1]=s[i]==s[j]?j+1:0;
    }
}
int main()
{
    int i,j;
    int n,m,_;
    scanf("%d",&_);
    while(_--){
        scanf("%s",str);
        int len=strlen(str);
        int ans=len;
        getfail(str,strlen(str));
        int round=len-f[len];
        if(len==round) printf("%d\n",round);
        else if(len%round==0) printf("%d\n",0);
        else{
            ans=(round-len%round);
            printf("%d\n",ans);
        }
    }
    return 0;
}

【HDU 4300】Clairewd’s message

扩展KMP的水题,输入两行,第一行是密码表

第二行是暗文和明文混在一起的文字,问:最短暗文和明文都存在的字符串是什么

因此必然想到,将此串的文字全部翻译,然后计算原串后缀与模版串的最长前缀的相同长度

还有一点需要注意的是,暗文肯定不会比密文短,因此记得判断一下

#include<cstdio>
#include<cstring>
#include<algorithm>
#define REP(a,b) for(i=a;i<b;i++)
using namespace std;
const int SIZEN=100005;
char s1[SIZEN],s2[SIZEN];
char table[SIZEN];
char change[SIZEN];
int next[SIZEN],extend[SIZEN];
void EKMP(char s[],char t[]){
    int i,j,p,l;
    int len=strlen(t);
    int len1=strlen(s);
    memset(next,0,sizeof(next));
    memset(extend,0,sizeof(extend));
    next[0]=len;
    j=0;
    while(1+j<len&&t[j]==t[1+j]) j++;
    next[1]=j;
    int a=1;
    for(i=2;i<len;i++){
        p=next[a]+a-1;
        l=next[i-a];
        if(i+l<p+1) next[i]=l;
        else{
            j=max(0,p-i+1);
            while(i+j<len&&t[i+j]==t[0+j]) j++;
            next[i]=j;
            a=i;
        }
    }
    j=0;
    while(j<len1&&j<len&&s[j]==t[j]) j++;
    extend[0]=j;
    a=0;
    for(i=1;i<len1;i++){
        p=extend[a]+a-1;
        l=next[i-a];
        if(l+i<p+1) extend[i]=l;
        else{
            j=max(0,p-i+1);
            while(i+j<len1&&j<len&&s[i+j]==t[j]) j++;
            extend[i]=j;
            a=i;
        }
    }
}
int main()
{
    int i,j;
    int n,m,_;
    scanf("%d",&_);
    while(_--){
        scanf("%s",table);
        REP(0,26) change[table[i]-'a'+1]=i+'a';
        scanf("%s",s1);
        int len=strlen(s1);
        REP(0,len) s2[i]=change[s1[i]-'a'+1];
        EKMP(s1,s2);
        int ans;
        REP(0,len+1)
            if(extend[i]+i==len&&i>=extend[i]){
                ans=i;
                break;
            }
        REP(0,ans) printf("%c",s1[i]);
        REP(0,ans) printf("%c",change[s1[i]-'a'+1]);
        printf("\n");
    }
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值