相关知识总结
训练题总结
其实 KMP 不需要太明白原理直接套模板就可以水过去大部分基础题,但是最近在做 [kuangbin带你飞]专题十六 KMP&扩展KMP&Manacher 发现考察 nex[]
数组原理的很多,所以在此记录做过的练习题
题意 :给你两个长度分别为 n(1<= N <= 1000000)和 m(1 <= M <= 10000)的序列 a[] 和 b[],求 b[] 序列在 a[] 序列中出现的首位置,如果没有输出 -1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <bits/stdc++.h> using namespace std ;typedef long long ll;const int N=1000005 ;int len1,len2;int tex[N],patt[N];int nex[N];void getnex (int str[],int len) { nex[0 ]=-1 ; for (int i=1 ,j=-1 ;i<len;i++){ while (j!=-1 &&str[i]!=str[j+1 ])j=nex[j]; if (str[i]==str[j+1 ])j++; nex[i]=j; } } int kmp2 (int tex[],int patt[]) { int cnt=0 ; getnex(patt,len2); for (int i=0 ,j=-1 ;i<len1;i++){ while (j!=-1 &&tex[i]!=patt[j+1 ])j=nex[j]; if (patt[j+1 ]==tex[i])j++; if (j==len2-1 )return (i+2 -len2); } return 0 ; } signed main () { ios::sync_with_stdio(false ),cin .tie(0 ); int t;cin >>t; while (t--){ cin >>len1>>len2; for (int i=0 ;i<len1;i++)cin >>tex[i]; for (int i=0 ;i<len2;i++)cin >>patt[i]; int flag=kmp2(tex,patt); if (flag)cout <<flag<<endl ; else cout <<-1 <<endl ; } return 0 ; }
题意 :给出匹配串和模式串,让求出模式串在匹配串中出现了几次(串之间可以重叠)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 #include <bits/stdc++.h> using namespace std ;typedef long long ll;const int N=1000005 ;char tex[N],patt[N];int nex[N];inline void getnex (char str[],int len) { nex[0 ]=-1 ; for (int i=1 ,j=-1 ;i<len;i++){ while (j!=-1 &&str[i]!=str[j+1 ])j=nex[j]; if (str[i]==str[j+1 ])j++; nex[i]=j; } } inline int kmp2 (char tex[],char patt[]) { int len1=strlen (tex),len2=strlen (patt),cnt=0 ; getnex(patt,len2); for (int i=0 ,j=-1 ;i<len1;i++){ while (j!=-1 &&tex[i]!=patt[j+1 ])j=nex[j]; if (patt[j+1 ]==tex[i])j++; if (j==len2-1 )cnt++,j=nex[j]; } return cnt; } signed main () { int t;scanf ("%d" ,&t); while (t--){ scanf ("%s%s" ,&patt,&tex); printf ("%d\n" ,kmp2(tex,patt)); } return 0 ; }
题意 :给出匹配串和模式串,让求出模式串在匹配串中出现了几次(串之间不可以重叠)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 #include <bits/stdc++.h> using namespace std ;typedef long long ll;const int N=1005 ;char tex[N],patt[N];int nex[N];void getnex (char str[],int len) { nex[0 ]=-1 ; for (int i=1 ,j=-1 ;i<len;i++){ while (j!=-1 &&str[i]!=str[j+1 ])j=nex[j]; if (str[i]==str[j+1 ])j++; nex[i]=j; } } int kmp2 (char tex[],char patt[]) { int len1=strlen (tex),len2=strlen (patt),cnt=0 ; getnex(patt,len2); for (int i=0 ,j=-1 ;i<len1;i++){ while (j!=-1 &&tex[i]!=patt[j+1 ])j=nex[j]; if (patt[j+1 ]==tex[i])j++; if (j==len2-1 )cnt++,j=-1 ; } return cnt; } signed main () { ios::sync_with_stdio(false ),cin .tie(0 ); while (cin >>tex&&tex[0 ]!='#' ){ cin >>patt; cout <<kmp2(tex,patt)<<endl ; } return 0 ; }
题意 :给你一串字符串,问你至少需要添加多少字符才能使它至少循环两次
思想 : 题解1 题解2 简单来说就是发现的一个规律,若串存在它的最小循环节一定满足:字符串的长度减去最后一个字符位置的 nex 值(即前缀长度)。
举个例子可以看出来,比如 abcabcabcabc
,next[len]=9,所以 len-next[len] 肯定是 len 的约数,并且此时 len-next[len] 也肯定为最短循环节。也可以这么想,如果可以整除,那么肯定存在最短循环节,因为如果能整除你那么肯定前缀跟后缀字符串存在重叠,并且可以分为n个一样的子字符串。
总结规律如下:
如果 len%(len-suffix[len])==0
,那么字符串本身存在最小循环节长度为 len-suffix[len]
如果 len%(len-suffix[len])!=0
,若是前缀和后缀不重叠一定不存在循环节
如果 len%(len-suffix[len])!=0
,若是前缀和后缀重叠但是不能整除说明有残缺部分,不存在循环节
这题问需要最少几个字符来让串本身循环两次及以上,无疑需要补充字符的时候我们选择的循环节长度越小那么需要补充的字符数也就越少了,那么我们先利用上述公式求出假定该字符串本身循环时的最小循环节,然后再看是否整除,整除说明本身就是循环,反之说明需要补充字符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include <bits/stdc++.h> using namespace std ;typedef long long ll;const int N=1e5 +5 ;char a[N];int nex[N];void getnex (char str[],int len) { nex[0 ]=-1 ; for (int i=1 ,j=-1 ;i<len;i++){ while (j!=-1 &&str[i]!=str[j+1 ])j=nex[j]; if (str[i]==str[j+1 ])j++; nex[i]=j; } } void solve () { cin >>a; int n=strlen (a); getnex(a,n); int len=n-(nex[n-1 ]+1 ); if (n%len==0 &&len!=n)cout <<0 <<endl ; else cout <<len-n%len<<endl ; } signed main () { ios::sync_with_stdio(false ),cin .tie(0 ); int t;cin >>t; while (t--){ solve(); } return 0 ; }
题意 :给一个字符串,问这个字符串最多可以由一个子串重复多少次得到
思想 :假定该字符串循环先求出最小循环节,然后再看看是不是真的循环,是的话也保证了求出来的次数使最多的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #include <iostream> #include <cstring> using namespace std ;const int N=1e7 +5 ;char a[N];int nex[N];void getnex (char str[],int len) { nex[0 ]=-1 ; for (int i=1 ,j=-1 ;i<len;i++){ while (j!=-1 &&str[i]!=str[j+1 ])j=nex[j]; if (str[i]==str[j+1 ])j++; nex[i]=j; } } signed main () { ios::sync_with_stdio(false ),cin .tie(0 ); while (cin >>a){ if (a[0 ]=='.' )break ; int n=strlen (a); getnex(a,n); int len=n-(nex[n-1 ]+1 ); if (n%len==0 )cout <<n/len<<endl ; else cout <<1 <<endl ; } return 0 ; }
题意 :给你一个字符串,求这个字符串到第 i 个字符为止的循环节的最大次数
思想 :上一道题的进阶题,这次是到找第 i 个字符之前为止的循环节的最大次数,其实就是多了一层循环。把这个字符串遍历一次即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <bits/stdc++.h> using namespace std ;typedef long long ll;const int N=1000005 ;int n;char s[N];int nex[N];void getnex (char str[],int len) { nex[0 ]=-1 ; for (int i=1 ,j=-1 ;i<len;i++){ while (j!=-1 &&str[i]!=str[j+1 ])j=nex[j]; if (str[i]==str[j+1 ])j++; nex[i]=j; } } signed main () { ios::sync_with_stdio(false ),cin .tie(0 ); int count=1 ; while (cin >>n&&n){ cin >>s; getnex(s,n); cout <<"Test case #" <<count++<<endl ; for (int i=1 ;i<n;i++){ if (nex[i]!=-1 &&((i+1 )%(i-nex[i])==0 )) cout <<i+1 <<" " <<(i+1 )/(i-nex[i])<<endl ; } cout <<endl ; } return 0 ; }
题意 :找出所给串的所有前缀长度,使得所给串中这个长度的前缀==这个长度的后缀
思路 :1~nex[] 求出来的位置组成的串就是满足条件,那么找出所有 nex[] 不为 0 的就可以了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> #include <cstring> using namespace std ;typedef long long ll;const int N=1e7 +5 ;char a[N];int nex[N],ans[N];void getnex (char str[],int len) { nex[0 ]=-1 ; for (int i=1 ,j=-1 ;i<len;i++){ while (j!=-1 &&str[i]!=str[j+1 ])j=nex[j]; if (str[i]==str[j+1 ])j++; nex[i]=j; } } signed main () { ios::sync_with_stdio(false ),cin .tie(0 ); while (cin >>a){ int len=strlen (a); getnex(a,len); int tot=0 ,p=len-1 ; while (p>=0 ){ ans[tot++]=nex[p]+1 ; p=nex[p]; } for (int i=tot-2 ;i>=0 ;i--) cout <<ans[i]<<" " ; cout <<len<<endl ; } return 0 ; }
题意 :给出一组字符串,找出这组字符串公有的长度最长的子串,如果存在多个选择字典序小的那个子串
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 #include <iostream> #include <vector> #include <algorithm> using namespace std ;typedef long long ll;const int N=65 ;int m;int nex[N];vector <string > vec;void init () { vec.clear(); } bool cmp (string a,string b) { return a.length()<b.length(); } void getnex (string str,int len) { nex[0 ]=-1 ; for (int i=1 ,j=-1 ;i<len;i++){ while (j!=-1 &&str[i]!=str[j+1 ])j=nex[j]; if (str[i]==str[j+1 ])j++; nex[i]=j; } } bool kmp (string patt) { int cnt=0 ; int len2=patt.length(); getnex(patt,len2); for (int k=1 ;k<m;k++){ string tex=vec[k]; int len1=tex.length(); for (int i=0 ,j=-1 ;i<len1;i++){ while (j!=-1 &&tex[i]!=patt[j+1 ])j=nex[j]; if (tex[i]==patt[j+1 ])j++; if (j==len2-1 ){ cnt++;break ; } } } if (cnt==m-1 )return true ; else return false ; } void solve () { cin >>m; string s; for (int i=0 ;i<m;i++) cin >>s,vec.push_back(s); sort(vec.begin(),vec.end(),cmp); string tot=vec[0 ],ans="" ; for (int i=0 ;i<=57 ;i++){ string temp="" ; for (int j=i;j<60 ;j++){ temp+=tot[j]; if (j-i+1 <3 )continue ; if (kmp(temp)){ if (temp.length()>ans.length())ans=temp; else if (ans.length()==temp.length()&&temp<ans)ans=temp; } } } if (ans=="" )cout <<"no significant commonalities" <<endl ; else cout <<ans<<endl ; } signed main () { ios::sync_with_stdio(false ),cin .tie(0 ); int t;cin >>t; while (t--){ init(); solve(); } return 0 ; }
题意 :给两个字符串 str1 和 str2, 求出是 str1 的前缀且是 str2 的后缀的最长的字符串
思想 :将两个串和并,然后求一遍nex[] 就可以了,然后取 nex[] 中值大的那个,但是需要注意的是前后缀都不能超过 str1 和 str2 的本身长度,所以需要特判一下能否取值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <iostream> using namespace std ;const int N=1e5 +5 ;string a,b;int nex[N];inline void getnex (string str,int len) { nex[0 ]=-1 ; for (int i=1 ,j=-1 ;i<len;i++){ while (j!=-1 &&str[i]!=str[j+1 ])j=nex[j]; if (str[i]==str[j+1 ])j++; nex[i]=j; } } inline void solve () { int lena=a.length(),lenb=b.length(); string temp=a+b; int lent=lena+lenb; getnex(temp,lent); int ans=nex[lent-1 ]+1 ; if (ans==0 ){ cout <<0 <<endl ; return ; } if (ans>lena||ans>lenb)ans=min(lena,lenb); for (int i=0 ;i<ans;i++) cout <<temp[i]; cout <<" " <<ans<<endl ; } signed main () { ios::sync_with_stdio(false ),cin .tie(0 ); while (cin >>a>>b){ solve(); } return 0 ; }