[KMP专题]
终于会了KMP。。感谢yzfdalao提供的blog讲解%%%
我也就不讲KMP怎么做的了,就讲几道题目吧(因为我辣鸡了,根本不会讲)。。
题目链接:https://vjudge.net/contest/185289(据说只有xj的小盆友可以进哦)
从最简单的讲起。
E题
给你一个匹配串和模式串,求模式串在匹配串中出现的次数。
模板题 ,直接构造fail数组,然后在匹配串中匹配,计算j=length(模式串)的数量。
C题
经典的求最短循环子串的问题。即在一个串中求出一个字串,复制n遍以后可以完全匹配原串。
显然,这个串的左端点必然为1。其次,有一个结论,如果n%(n-fail[n])==0,则最短循环子串长为n-fail[n]。
否则长度为n(原串)。
让我们来简单证明一下。
fail[n]也可以代表s[1..fail[n]]=s[n-fail[n]+1..n],那么,相当于——
(在上图中上下两串匹配,lenght(s1)=n-fail[n],其中s1'=s1,s2'=s2,s3'=s3....sk'=sk,由于长度都相等,所以中间这一些s1=s1'=s2=s2'=s3=s3'=s4=s4'...=sk=sk')
不难得出,n-faill[n] (length(s1))即为最小循环子串的长度。当然,n%(n-fail[n])!=0的情况就代表中间这一块不能被刚好填满,所以n-fail[n]在这种情况下也不能作为最小循环子串的长度。
B题
求两串的所有公共子串的数量。
显然,我们需要求出对于每个串来说自己的最小循环字串(因为两个串的所有公共字串总会以一个非循环串为基础)。
然后,如果两者的最小循环子串不同,代表不可能会有相同的子串,直接puts0。
否则,所有公共子串的长度肯定是最小循环子串的长度的整倍数又不超长度,那枚举一发就可以出答案了。
code:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int N=1000005; 6 char s1[N],s2[N]; 7 int n,m,f1[N],f2[N],l1,l2; 8 int ans; 9 int main() { 10 scanf("%s%s",s1+1,s2+1); 11 n=strlen(s1+1),m=strlen(s2+1); 12 memset(f1,0,sizeof f1); 13 for (int i=2,j=0; i<=n; i++) { 14 while (j>0&&s1[i]!=s1[j+1]) j=f1[j]; 15 if (s1[i]==s1[j+1]) j++; 16 f1[i]=j; 17 } 18 if (n%(n-f1[n])!=0) l1=n; else l1=n-f1[n]; 19 memset(f2,0,sizeof f2); 20 for (int i=2,j=0; i<=m; i++) { 21 while (j>0&&s2[i]!=s2[j+1]) j=f2[j]; 22 if (s2[i]==s2[j+1]) j++; 23 f2[i]=j; 24 } 25 if (m%(m-f2[m])!=0) l2=m; else l2=m-f2[m]; 26 if (l1!=l2) {puts("0"); return 0;} 27 for (int i=1; i<=l1; i++) if (s1[i]!=s2[i]) { 28 puts("0"); return 0; 29 } 30 for (int i=1; i<=n/l1&&i<=m/l1; i++) 31 if (n%i==0&&m%i==0){ 32 int u=n/i,v=m/i; 33 if (u%l1==0&&v%l1==0) ans++; 34 } 35 printf("%d",ans); 36 return 0; 37 }
A题
一道比较复杂的题目。开始被题意杀。题意就是一个串,正着来是否能先经过A串,再经过B串(不能重叠)。然后反过来能不能。
处理地复杂一点,也是挺适用于kmp的吧。
code:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define Ms(a,x) memset(a,x,sizeof a) 5 using namespace std; 6 const int N=100005; 7 char tar[N],a[N],b[N]; 8 int n,m,f1[N],f2[N],l1,l2; 9 bool g1[N],g2[N],jug1,jug2; 10 int main() { 11 scanf("%s%s%s",tar+1,a+1,b+1),n=strlen(tar+1); 12 l1=strlen(a+1),l2=strlen(b+1),jug1=jug2=0; 13 Ms(f1,0),Ms(f2,0); 14 Ms(g1,0),Ms(g2,0); 15 for (int i=2,j=0; i<=l1; i++) { 16 while (j>0&&a[i]!=a[j+1]) j=f1[j]; 17 if (a[i]==a[j+1]) j++; 18 f1[i]=j; 19 } 20 for (int i=2,j=0; i<=l2; i++) { 21 while (j>0&&b[i]!=b[j+1]) j=f2[j]; 22 if (b[i]==b[j+1]) j++; 23 f2[i]=j; 24 } 25 for (int i=1,j=0; i<=n; i++) { 26 while (j>0&&tar[i]!=a[j+1]) j=f1[j]; 27 if (tar[i]==a[j+1]) j++; 28 if (j==l1) g1[i]=1; 29 } 30 for (int i=1,j=0; i<=n; i++) { 31 while (j>0&&tar[i]!=b[j+1]) j=f2[j]; 32 if (tar[i]==b[j+1]) j++; 33 if (j==l2) g2[i]=1; 34 } 35 for (int i=1; i<=n; i++) if (g1[i]) { 36 for (int j=i+l2; j<=n; j++) if (g2[j]) jug1=1; 37 break; 38 } 39 reverse(tar+1,tar+1+n); 40 Ms(f1,0),Ms(f2,0); 41 Ms(g1,0),Ms(g2,0); 42 for (int i=2,j=0; i<=l1; i++) { 43 while (j>0&&a[i]!=a[j+1]) j=f1[j]; 44 if (a[i]==a[j+1]) j++; 45 f1[i]=j; 46 } 47 for (int i=2,j=0; i<=l2; i++) { 48 while (j>0&&b[i]!=b[j+1]) j=f2[j]; 49 if (b[i]==b[j+1]) j++; 50 f2[i]=j; 51 } 52 for (int i=1,j=0; i<=n; i++) { 53 while (j>0&&tar[i]!=a[j+1]) j=f1[j]; 54 if (tar[i]==a[j+1]) j++; 55 if (j==l1) g1[i]=1; 56 } 57 for (int i=1,j=0; i<=n; i++) { 58 while (j>0&&tar[i]!=b[j+1]) j=f2[j]; 59 if (tar[i]==b[j+1]) j++; 60 if (j==l2) g2[i]=1; 61 } 62 for (int i=1; i<=n; i++) if (g1[i]) { 63 for (int j=i+l2; j<=n; j++) if (g2[j]) jug2=1; 64 break; 65 } 66 if (jug1&&jug2) puts("both"); else 67 if (jug1) puts("forward"); else 68 if (jug2) puts("backward"); else 69 puts("fantasy"); 70 return 0; 71 }
F题
二维的最小循环子串,只是这里的要求比较宽,不需要完全覆盖,只是要求最小循环子串无限扩展后完全包含原串。
显然,只要对两个方向整体去求一下fail数组,然后最短循环子串一个方向上长度就是n-fail[n](这里并不用考虑能不能刚好铺满)。
然后同样的方法求另一个方向上的。最后两个长度相乘就行了。注意是要整体比较,不然绝对没有正确性可言。(因为我们需要的是“整体”循环)
code:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define Ms(a,x) memset(a,x,sizeof a) 5 using namespace std; 6 const int N=10005,M=75; 7 int n,m,ans,fail[N]; 8 char a[N][M]; 9 bool jug_row(int x,int y) { 10 for (int i=1; i<=m; i++) 11 if (a[x][i]!=a[y][i]) return 0; 12 return 1; 13 } 14 bool jug_col(int x,int y) { 15 for (int i=1; i<=n; i++) 16 if (a[i][x]!=a[i][y]) return 0; 17 return 1; 18 } 19 int main() { 20 scanf("%d%d",&n,&m); 21 for (int i=1; i<=n; i++) scanf("%s",a[i]+1); 22 Ms(fail,0); 23 for (int i=2,j=0; i<=n; i++) { 24 while (j>0&&!jug_row(i,j+1)) j=fail[j]; 25 if (jug_row(i,j+1)) j++; 26 fail[i]=j; 27 } 28 ans=n-fail[n]; 29 Ms(fail,0); 30 for (int i=2,j=0; i<=m; i++) { 31 while (j>0&&!jug_col(i,j+1)) j=fail[j]; 32 if (jug_col(i,j+1)) j++; 33 fail[i]=j; 34 } 35 ans*=m-fail[m]; 36 printf("%d",ans); 37 return 0; 38 }
I题
求一个串的每一个前缀在串中总共出现了几次。直接大力dp。
f[i]=f[fail[i]]+1,ans=Σ(i=1,n)f[i]
为什么可以这样做?相当于s[1..fail[i]]=s[i-fail[i]+1..i],也就是说,后者有前者具备的前者的所有子串,方案数相当于又多了f[fail[i]],然后又出现了一个新子串s[1..i],所以这是正确的。
那为什么能保证方案数不算少呢?因为我们知道,fail[i]是离i最远的满足s[1..fail[i]]=s[i-fail[i]+1..i]的点,不会有更远的点,也就是说,不会有被漏算的串。
然后又保证了s[i-fail[i]+1..i]里没有被计算到的子串会被新计算到一次(因为f[fail[i]]就满足了正确性)。
code:
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define Ms(a,x) memset(a,x,sizeof a) 5 using namespace std; 6 const int N=200005,TT=10007; 7 int n,m,ans,fail[N],cnt[N]; 8 char a[N]; 9 int main() { 10 int T; scanf("%d",&T); 11 for (; T; T--) { 12 scanf("%d%s",&n,a+1); 13 Ms(fail,0),Ms(cnt,0),ans=0; 14 for (int i=2,j=0; i<=n; i++) { 15 while (j>0&&a[i]!=a[j+1]) j=fail[j]; 16 if (a[i]==a[j+1]) j++; 17 fail[i]=j; 18 } 19 for (int i=1; i<=n; i++) cnt[i]=cnt[fail[i]]+1; 20 for (int i=1; i<=n; i++) ans=(ans+cnt[i])%TT; 21 printf("%d\n",ans); 22 } 23 return 0; 24 }
终于学会了困扰已久的kmp,只是kmp这个思想实在需要细细回味!!