KMP
Posted Winniechen
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了KMP相关的知识,希望对你有一定的参考价值。
KMP
本质上,kmp就是维护出了一个字符串的前缀的next,并且依据next的某些性质进行字符串匹配。
next:就是最长的前缀和后缀相等的长度
next[i]必定从某一个next[...next[i]]]中得到的,满足s[i]=s[next[i]];
而匹配的时候,满足如果i和j失配,那么必定存在某个s[next[..next[j-1]]+1]=s[i];
例题时间:
[Usaco2015 Feb]Censoring BZOJ3942
分析:
kmp裸题,很显然,我们贪心的将能删除的全部删除就可以了,那么我们如何维护后缀呢?
用栈来维护后缀,考虑将每个主串的对于模式串的匹配位置存一下,之后继续往下匹配就可以了
附上代码:
#include <cstdio> #include <algorithm> #include <queue> #include <cstring> #include <cstdlib> #include <cmath> #include <iostream> #include <set> using namespace std; #define N 1000005 char str[N],sub[N],sta[N]; int pos[N],top,nxt[N],n,m; void get_next() { int j=0;nxt[1]=0; for(int i=2;i<=n;i++) { while(j&&sub[j+1]!=sub[i])j=nxt[j]; nxt[i]=(sub[j+1]==sub[i])?++j:0; } } int main() { scanf("%s%s",str+1,sub+1);n=strlen(str+1),m=strlen(sub+1); get_next(); int j=0; for(int i=1;i<=n;i++) { j=pos[top];sta[++top]=str[i]; while(j&&sub[j+1]!=str[i])j=nxt[j]; if(str[i]==sub[j+1])j++; if(j==m)top-=m; else pos[top]=j; } for(int i=1;i<=top;i++)printf("%c",sta[i]);puts(""); return 0; }
BZOJ1355: [Baltic2009]Radio Transmission
结论题:
最短的循环节必定为n-next[n];
证明:如果存在更短的循环节,那么next[i]必定会比当前的更大。并且,s[n-nxt[i]...0]=s[n-nxt[i]-0...nxt[i]];
因此,s必定为以s[1...n-nxt[n]]不停重复+去掉结尾构成的;
附上代码:
#include <cstdio> #include <algorithm> #include <queue> #include <cstring> #include <cstdlib> #include <cmath> #include <iostream> #include <set> using namespace std; #define N 1000005 char str[N],sub[N],sta[N]; int pos[N],top,nxt[N],n,m; void get_next() { int j=0;nxt[1]=0; for(int i=2;i<=n;i++) { while(j&&sub[j+1]!=sub[i])j=nxt[j]; nxt[i]=(sub[j+1]==sub[i])?++j:0; } } int main() { scanf("%*d%s",sub+1);n=strlen(sub+1); get_next(); printf("%d\n",n-nxt[n]); /* int j=0; for(int i=1;i<=n;i++) { j=pos[top];sta[++top]=str[i]; while(j&&sub[j+1]!=str[i])j=nxt[j]; if(str[i]==sub[j+1])j++; if(j==m)top-=m; else pos[top]=j; } for(int i=1;i<=top;i++)printf("%c",sta[i]);puts(""); */ return 0; }
BZOJ3670: [Noi2014]动物园
分析:
我们考虑其实答案就是while(i){if(i<=x/2)num++;i=next[i]}
所以,我们将i的所有next[i]有多少个存一下,之后如果存在i<=x/2那么,所有i的子串都满足<=x/2
因此,我们每次只要找到第一个i<=x/2的就可以了,如果暴力去找会TLE
那么,我们考虑如果i>x/2那么i+1>(x+1)/2;因此,每次找的时候,直接找上一次找到的下一个就可以了。
附上代码:
#include <cstdio> #include <algorithm> #include <queue> #include <cstring> #include <cstdlib> #include <cmath> #include <iostream> #include <set> using namespace std; #define N 1000005 #define mod 1000000007 char str[N],sub[N],sta[N]; int pos[N],top,nxt[N],n,m;int vis[N]; void get_next() { int i=0,j=-1;nxt[0]=-1;vis[0]=0; while(i<m) { if(j==-1||sub[j]==sub[i]) { nxt[++i]=++j; vis[i]=vis[j]+1; }else j=nxt[j]; } } int main() { int T; scanf("%d",&T); while(T--) { memset(vis,0,sizeof(vis)); scanf("%s",sub);m=strlen(sub); get_next(); long long ans=1;int j=0; for(int i=1;i<m;i++) { //printf("%lld\n",ans); while(j>=0&&sub[i]!=sub[j])j=nxt[j];j++; while((j<<1)>i+1)j=nxt[j]; //printf("%d\n",j); ans=ans*(vis[j]+1)%mod; } printf("%lld\n",ans); } /* int j=0; for(int i=1;i<=n;i++) { j=pos[top];sta[++top]=str[i]; while(j&&sub[j+1]!=str[i])j=nxt[j]; if(str[i]==sub[j+1])j++; if(j==m)top-=m; else pos[top]=j; } for(int i=1;i<=top;i++)printf("%c",sta[i]);puts(""); */ return 0; }
BZOJ1511:[POI2006]OKR-Periods of Words
分析:
看起来很像前面的某一道题,只是变成了最长循环节...那么就是找到一个最小的i满足前缀后缀相同就可以了
附上代码:
#include <cstdio> #include <algorithm> #include <queue> #include <cstring> #include <cstdlib> #include <cmath> #include <iostream> #include <set> using namespace std; #define N 1000005 char s[N];int n,nxt[N]; long long ans; void get_next() { nxt[1]=0;int j=0; for(int i=2;i<=n;i++) { while(j&&s[i]!=s[j+1])j=nxt[j]; nxt[i]=(s[i]==s[j+1])?++j:0; } } int main() { scanf("%d%s",&n,s+1);get_next(); for(int i=1;i<=n;i++) { while(nxt[nxt[i]])nxt[i]=nxt[nxt[i]]; if(nxt[i])ans+=i-nxt[i]; } printf("%lld\n",ans); return 0; }
BZOJ3620: 似乎在梦中见过的样子
分析:
n=15000的n^2题实在是太毒了...
几乎和动物园是一模一样的,只是多了一个K而已,那么就是每次更新num的时候判断一下是否大于K就好了,之后枚举起点,剩下的和动物园一样了
附上代码:
#include <cstdio> #include <algorithm> #include <queue> #include <cstring> #include <cstdlib> #include <cmath> #include <iostream> #include <set> using namespace std; #define N 1000005 #define mod 1000000007 char str[N],sub[N],sta[N]; int pos[N],top,nxt[N],n,m,k; void get_next() { int i=0,j=-1;nxt[0]=-1; while(i<m) { if(j==-1||sub[j]==sub[i]) { nxt[++i]=++j; }else j=nxt[j]; } } int main() { scanf("%s%d",sub,&k);m=strlen(sub); long long ans=0; while(m) { get_next();int j=0; for(int i=1;i<m;i++) { while(j>=0&&sub[i]!=sub[j])j=nxt[j];j++; while((j<<1)>=i+1)j=nxt[j]; if(j>=k)ans++; } for(int i=1;i<=m;i++)sub[i-1]=sub[i]; m=strlen(sub); } printf("%lld\n",ans); /* int j=0; for(int i=1;i<=n;i++) { j=pos[top];sta[++top]=str[i]; while(j&&sub[j+1]!=str[i])j=nxt[j]; if(str[i]==sub[j+1])j++; if(j==m)top-=m; else pos[top]=j; } for(int i=1;i<=top;i++)printf("%c",sta[i]);puts(""); */ return 0; }
BZOJ1009: [HNOI2008]GT考试
分析:
说实话,之前并不敢写这道题,写完发现这题水的一批...
如果长度没有那么长的话,之间f[i][j]表示第i个字符匹配到了不吉利数字的第j个...之后用next转移就可以了
那么长度长一点那么就矩阵乘法呗...其实可以不用KMP求next的,但是我觉得暴力求比KMP求还麻烦
附上代码:
#include <cstdio> #include <algorithm> #include <queue> #include <cstring> #include <cstdlib> #include <cmath> #include <iostream> #include <set> using namespace std; #define N 25 int n,m,mod,nxt[N];char s[N]; struct node { int a[N][N]; friend node operator*(const node &d,const node &b) { node c;memset(c.a,0,sizeof(c.a)); for(int i=0;i<m;i++) { for(int j=0;j<m;j++) { for(int k=0;k<m;k++) { c.a[i][j]=(c.a[i][j]+d.a[i][k]*b.a[k][j])%mod; } } } return c; } }ret,map; void get_next() { int j=0;nxt[1]=0; for(int i=2;i<=m;i++) { while(j&&s[j+1]!=s[i])j=nxt[j]; nxt[i]=(s[j+1]==s[i])?++j:0; } } void q_pow(int n) { for(int i=0;i<m;i++)ret.a[i][i]=1; while(n) { if(n&1)ret=ret*map; map=map*map;n=n>>1; } return ; } void print(const node &d) { for(int i=0;i<m;i++) { for(int j=0;j<m;j++) { printf("%d ",d.a[i][j]); } puts(""); } } int vis[N],sum; int main() { scanf("%d%d%d",&n,&m,&mod); scanf("%s",s+1);get_next(); for(int i=0;i<m;i++) { memset(vis,0,sizeof(vis)); int j=i,num=0; while(1) { if(!vis[s[j+1]-‘0‘]) { vis[s[j+1]-‘0‘]=1,num++; if(j+1<m)map.a[i][j+1]=1; } if(!j)break; j=nxt[j]; } map.a[i][0]=10-num; } //print(map); q_pow(n); int ans=0; for(int i=0;i<m;i++) { ans=(ans+ret.a[0][i])%mod; } printf("%d\n",ans); }
以上是关于KMP的主要内容,如果未能解决你的问题,请参考以下文章