666 专题三 KMP & 扩展KMP & Manacher
KMP:
Problem A.Number Sequence
d.求子串首次出现在主串中的位置
s.
c.
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; #define MAXN 10005//字符串长度 int a[1000005]; int b[MAXN]; int _next[MAXN]; void GetNext(int t[],int M){//求next数组 int j,k,len; j=0;//从0开始,首先求_next[1] k=-1;//比较指针 _next[0]=-1;//初始值-1 //len=strlen(t); len=M; while(j<len){ if(k==-1||t[j]==t[k]){//指针到头了,或者相等 ++j; ++k; _next[j]=k;//此句可由优化替代 /*优化(求匹配位置时可用) if(t[j]!=t[k])_next[j]=k; else _next[j]=_next[k]; //*/ } else k=_next[k]; } } int KMPIndex(int s[],int t[],int N,int M){//求子串首次出现在主串中的位置 int i,j,lens,lent; i=j=0; //lens=strlen(s); //lent=strlen(t); lens=N; lent=M; while(i<lens&&j<lent){ if(j==-1||s[i]==t[j]){ ++i; ++j; } else j=_next[j]; } if(j>=lent)return i-lent+1; else return -1; } int main(){ int T; int N,M; scanf("%d",&T); while(T--){ scanf("%d%d",&N,&M); for(int i=0;i<N;++i){ scanf("%d",&a[i]); } for(int i=0;i<M;++i){ scanf("%d",&b[i]); } GetNext(b,M); printf("%d\n",KMPIndex(a,b,N,M)); } return 0; }
Problem B.Oulipo
d.统计子串在主串中的出现次数,可重叠
s.
c.
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; #define MAXN 10005//字符串长度 char W[MAXN]; char T[1000005]; int _next[MAXN]; void GetNext(char t[]){//求next数组 int j,k,len; j=0;//从0开始,首先求_next[1] k=-1;//比较指针 _next[0]=-1;//初始值-1 len=strlen(t); while(j<len){ if(k==-1||t[j]==t[k]){//指针到头了,或者相等 ++j; ++k; _next[j]=k;//此句可由优化替代 /*优化(求匹配位置时可用) if(t[j]!=t[k])_next[j]=k; else _next[j]=_next[k]; //*/ } else k=_next[k]; } } int KMPCount(char s[],char t[]){//统计子串在主串中的出现次数,可重叠 int i,j,lens,lent,cnt; i=j=0; lens=strlen(s); lent=strlen(t); cnt=0; while(i<lens){ if(j==-1||s[i]==t[j]){ ++i; ++j; } else j=_next[j]; if(j==lent)++cnt; } return cnt; } int main(){ int TT; scanf("%d",&TT); while(TT--){ scanf("%s",W); scanf("%s",T); GetNext(W); printf("%d\n",KMPCount(T,W)); } return 0; }
Problem C.剪花布条
d.统计子串在主串中的出现次数,不可重叠
s.当j==lent时,直接让j=0;而不是j=_next[j];,就不重叠了~
c.
/* kmp模板 */ #include<iostream> #include<stdio.h> #include<string.h> using namespace std; #define MAXN 1024//字符串长度 int _next[MAXN]; void GetNext(char t[]){//求next数组 int j,k,len; j=0;//从0开始,首先求_next[1] k=-1;//比较指针 _next[0]=-1;//初始值-1 len=strlen(t); while(j<len){ if(k==-1||t[j]==t[k]){//指针到头了,或者相等 ++j; ++k; _next[j]=k;//此句可由优化替代 /*优化(求匹配位置时可用) if(t[j]!=t[k])_next[j]=k; else _next[j]=_next[k]; //*/ } else k=_next[k]; } } int KMPCount(char s[],char t[]){//统计子串在主串中的出现次数,不可重叠 int i,j,lens,lent,cnt; i=j=0; lens=strlen(s); lent=strlen(t); cnt=0; while(i<lens){ if(j==-1||s[i]==t[j]){ ++i; ++j; } else j=_next[j]; if(j==lent){ ++cnt; j=0;//不可重叠 } } return cnt; } int main(){ char str1[MAXN],str2[MAXN]; while(~scanf("%s",str1)){ if(str1[0]=='#')break; scanf("%s",str2); GetNext(str2); printf("%d\n",KMPCount(str1,str2)); } return 0; }
Problem D.Cyclic Nacklace
d.给出一串字符串,可以在字符串的开头的结尾添加字符,求添加最少的字符,使这个字符串是循环的(例如:abcab 在结尾添加1个c变为 abcabc 既可)。
s.求出最小循环节,看总长能不能整除。
最小循环节(长度)=len-next[len];
c.
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; #define MAXN 100005//字符串长度 char str[MAXN]; int _next[MAXN]; void GetNext(char t[]){//求next数组 int j,k,len; j=0;//从0开始,首先求_next[1] k=-1;//比较指针 _next[0]=-1;//初始值-1 len=strlen(t); while(j<len){ if(k==-1||t[j]==t[k]){//指针到头了,或者相等 ++j; ++k; _next[j]=k;//此句可由优化替代 /*优化(求匹配位置时可用) if(t[j]!=t[k])_next[j]=k; else _next[j]=_next[k]; //*/ } else k=_next[k]; } } int main(){ int T; int len; int len2;//最小循环节长度 scanf("%d",&T); while(T--){ scanf("%s",str); GetNext(str); len=strlen(str); len2=len-_next[len]; if(len2==len){ printf("%d\n",len2); } else{ if(len%len2==0){ printf("%d\n",0); } else{ printf("%d\n",len2-(len%len2)); } } } return 0; }
Problem E.Period
d.统计单串中从某个位置以前有多少重复的串(即判断位置i之前的串是不是循环的串。如果是,输出位置i 和循环的次数)
s.最小循环节(长度)=len-next[len];
对每个位置求出最小循环节,然后判断前面的串是不是循环的即可。
c.
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; #define MAXN 1000005//字符串长度 char str[MAXN]; int _next[MAXN]; void GetNext(char t[]){//求next数组 int j,k,len; j=0;//从0开始,首先求_next[1] k=-1;//比较指针 _next[0]=-1;//初始值-1 len=strlen(t); while(j<len){ if(k==-1||t[j]==t[k]){//指针到头了,或者相等 ++j; ++k; _next[j]=k;//此句可由优化替代 /*优化(求匹配位置时可用) if(t[j]!=t[k])_next[j]=k; else _next[j]=_next[k]; //*/ } else k=_next[k]; } } void KMPCount2(char t[]){//统计单串中从某个位置以前有多少重复的串 int i,lent,tmp; lent=strlen(t); for(i=2;i<=lent;++i){ tmp=i-_next[i]; if(i%tmp==0&&i/tmp>1) printf("\t位置:%d 个数:%d\n",i,i/tmp); } } int main(){ int N; int c=0; int tmp;//最小循环节长度 while(~scanf("%d",&N)){ if(N==0)break; scanf("%s",str); GetNext(str); printf("Test case #%d\n",++c); for(int i=2;i<=N;++i){ tmp=i-_next[i]; if( (i%tmp==0)&&(i/tmp>1) ){ printf("%d %d\n",i,i/tmp); } } printf("\n"); } return 0; }
Problem F.The Minimum Length
d.最小循环节
s.
c.
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; #define MAXN 1000005//字符串长度 char str[MAXN]; int _next[MAXN]; void GetNext(char t[]){//求next数组 int j,k,len; j=0;//从0开始,首先求_next[1] k=-1;//比较指针 _next[0]=-1;//初始值-1 len=strlen(t); while(j<len){ if(k==-1||t[j]==t[k]){//指针到头了,或者相等 ++j; ++k; _next[j]=k;//此句可由优化替代 /*优化(求匹配位置时可用) if(t[j]!=t[k])_next[j]=k; else _next[j]=_next[k]; //*/ } else k=_next[k]; } } int main(){ int len; int len2;//最小循环节长度 while(~scanf("%s",str)){ len=strlen(str); GetNext(str); len2=len-_next[len]; printf("%d\n",len2); } return 0; }
Problem G.Power Strings
d.求最大的循环次数
s.最小循环节
c.
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; #define MAXN 1000005//字符串长度 char str[MAXN]; int _next[MAXN]; void GetNext(char t[]){//求next数组 int j,k,len; j=0;//从0开始,首先求_next[1] k=-1;//比较指针 _next[0]=-1;//初始值-1 len=strlen(t); while(j<len){ if(k==-1||t[j]==t[k]){//指针到头了,或者相等 ++j; ++k; _next[j]=k;//此句可由优化替代 /*优化(求匹配位置时可用) if(t[j]!=t[k])_next[j]=k; else _next[j]=_next[k]; //*/ } else k=_next[k]; } } int main(){ int len; int len2;//最小循环节长度 while(~scanf("%s",str)){ if(str[0]=='.')break; len=strlen(str); GetNext(str); len2=len-_next[len]; if(len%len2==0){ printf("%d\n",len/len2); } else{ printf("1\n"); } } return 0; }
Problem H.Seek the Name, Seek the Fame
d.给出一个字符串str,求出str中存在多少子串,使得这些子串既是str的前缀,又是str的后缀。从小到大依次输出这些子串的长度。
s.所以对于这道题,求出len处的next值,并递归的向下求出所有的next值,得到的就是答案。
c.
/* kmp模板 */ #include<iostream> #include<stdio.h> #include<string.h> using namespace std; #define MAXN 400005//字符串长度 int _next[MAXN]; void GetNext(char t[]){//求next数组 int j,k,len; j=0;//从0开始,首先求_next[1] k=-1;//比较指针 _next[0]=-1;//初始值-1 len=strlen(t); while(j<len){ if(k==-1||t[j]==t[k]){//指针到头了,或者相等 ++j; ++k; _next[j]=k;//此句可由优化替代 /*优化(求匹配位置时可用) if(t[j]!=t[k])_next[j]=k; else _next[j]=_next[k]; //*/ } else k=_next[k]; } } int main(){ char str[MAXN]; int sum[MAXN]; int k,len; while(~scanf("%s",str)){ GetNext(str); k=0; len=strlen(str); for(int i=len;_next[i]>0;i=_next[i]){ sum[k++]=_next[i]; } for(int i=k-1;i>=0;--i){ printf("%d ",sum[i]); } printf("%d\n",len); } return 0; }
Problem I.Blue Jeans
d.求 N 个字符串的最长连续公共子串,N 范围是 10 ,每个串最长 60,所以可以暴力……
本来是没什么意思的,不过可以学习下string的几个函数
s.暴力。好像还能用后缀数组做,以后再看。
c.
#include<iostream> #include<stdio.h> #include<string> using namespace std; /* 涉及到string类的两个函数find和substr: 1、find函数 原型:size_t find ( const string& str, size_t pos = 0 ) const; 功能:查找子字符串第一次出现的位置。 参数说明:str为子字符串,pos为初始查找位置。 返回值:找到的话返回第一次出现的位置,否则返回string::npos 2、substr函数 原型:string substr ( size_t pos = 0, size_t n = npos ) const; 功能:获得子字符串。 参数说明:pos为起始位置(默认为0),n为结束位置(默认为npos) 返回值:子字符串 */ int main(){ int n; int m; string s[10]; scanf("%d",&n); while(n--){ scanf("%d",&m); for(int i=0;i<m;++i){ cin>>s[i]; } string ans=""; for(int i=60;i>=3;--i){//长度>=3 for(int j=0;j<=60-i;++j){ string tmp=s[0].substr(j,i);//从j开始取i长度的子串 bool flag=true; for(int k=1;k<m;++k){ if(s[k].find(tmp)==string::npos){ flag=false; break; } } if(flag){ if(ans==""){ ans=tmp; } else if(tmp<ans){ ans=tmp; } } } if(ans!="")break; } if(ans==""){ printf("no significant commonalities\n"); } else{ cout<<ans<<endl; } } return 0; }
Problem J.Simpsons’ Hidden Talents
d.两个字符串s1、s2,求s1和s2的最长的相同的s1前缀和s2后缀
s.先求s1的next数组,再求s2的next数组(即代码中_next2数组,此时不是自己与自己匹配,而是与s1匹配),最后看_next2[len2]即可(len2为串s2的长度)。
c.
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; #define MAXN 50005//字符串长度 char s1[MAXN]; char s2[MAXN]; int _next[MAXN];//s1的next数组,s1与自己匹配 int _next2[MAXN];//s2的next数组,s2与s1匹配 void GetNext(char t[]){//求next数组 int j,k,len; j=0;//从0开始,首先求_next[1] k=-1;//比较指针 _next[0]=-1;//初始值-1 len=strlen(t); while(j<len){ if(k==-1||t[j]==t[k]){//指针到头了,或者相等 ++j; ++k; _next[j]=k;//此句可由优化替代 /*优化(求匹配位置时可用) if(t[j]!=t[k])_next[j]=k; else _next[j]=_next[k]; //*/ } else k=_next[k]; } } void GetNext2(char s[],char t[]){//s的前部与t匹配,求t相对于s的next数组(_next2[]) int j,k,len; //注意这几个初始化与上面不同 j=0;//从0开始,首先求_next[1] k=0;//比较指针 _next2[0]=0;//初始值0 len=strlen(t); while(j<len){ if(k==-1||t[j]==s[k]){//指针到头了,或者相等 ++j; ++k; _next2[j]=k;//此句可由优化替代 /*优化(求匹配位置时可用) if(t[j]!=t[k])_next[j]=k; else _next[j]=_next[k]; //*/ } else k=_next[k]; } } int main(){ while(~scanf("%s%s",s1,s2)){ GetNext(s1); GetNext2(s1,s2); int len2=strlen(s2); if(_next2[len2]==0){ printf("0\n"); } else{ for(int i=0;i<_next2[len2];++i){ printf("%c",s1[i]); } printf(" %d\n",_next2[len2]); } } return 0; }
Problem K.Count the string
d.统计所有前缀在串中出现的次数和
s.next数组,递推
c.
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; #define MAXN 200005//字符串长度 #define MOD 10007 char s[MAXN]; int dp[MAXN]; int _next[MAXN]; void GetNext(char t[]){//求next数组 int j,k,len; j=0;//从0开始,首先求_next[1] k=-1;//比较指针 _next[0]=-1;//初始值-1 len=strlen(t); while(j<len){ if(k==-1||t[j]==t[k]){//指针到头了,或者相等 ++j; ++k; _next[j]=k;//此句可由优化替代 /*优化(求匹配位置时可用) if(t[j]!=t[k])_next[j]=k; else _next[j]=_next[k]; //*/ } else k=_next[k]; } } int main(){ int T; int n; int len; int ans; scanf("%d",&T); while(T--){ scanf("%d",&n); scanf("%s",s); GetNext(s); len=strlen(s); ans=0; dp[0]=0; for(int i=1;i<=len;++i){ dp[i]=dp[_next[i]]+1; dp[i]%=MOD; ans+=dp[i]; ans%=MOD; } printf("%d\n",ans); } return 0; }
Problem L.Clairewd’s message(此题再看看,未完成)
d.
给出26个英文字母的加密表,明文中的'a'会转为加密表中的第一个字母,'b'转为第二个,...依次类推。
然后第二行是一个字符串(str1),形式是密文+明文,其中密文一定完整,而明文可能不完整(也可能没有)。
求出最短的完整的字符串(密文+明文)。
s.
1.用kmp来做:
首先肯定的是,给定的串中明文长度一定小于等于密文。也就是说明文长度小于等于总长的一半。
于是,取总长的后一半作为主串,然后把串反翻译一遍得到str2,然后用str2与str1的后一半进行匹配。首次把str1的后一半匹配完的位置即是给定的串中明文开始的位置。
因为是首次,所以保证了前面的密文长度最小,即总长度最小。
然后输出密文+明文,即可。
2.用扩展kmp来做:
//next[i]:x[i...m-1]与x[0...m-1]的最长公公前缀
//extend[i]:y[i...n-1]与x[0...m-1]的最长公共前缀
可以用extend数组,根据它的意义,str1作为y,str2作为x,当 i+extend[i]==len1时代表y中的从i到结尾均与x的开头匹配,如果此时i大于串长度的一半,则满足条件。
此时的i即为实际密文(明文)的长度,然后按要求输出答案即可。
c.kmp来做
/* kmp模板 */ #include<iostream> #include<stdio.h> #include<string.h> using namespace std; #define MAXN 100005//字符串长度 int _next[MAXN]; void GetNext(char t[]){//求next数组 int j,k,len; j=0;//从0开始,首先求_next[1] k=-1;//比较指针 _next[0]=-1;//初始值-1 len=strlen(t); while(j<len){ if(k==-1||t[j]==t[k]){//指针到头了,或者相等 ++j; ++k; _next[j]=k;//此句可由优化替代 /*优化(求匹配位置时可用) if(t[j]!=t[k])_next[j]=k; else _next[j]=_next[k]; //*/ } else k=_next[k]; } } int KMPIndex(char s[],char t[]){//s的后缀等于t的前缀的最大长度 int i,j,lens,lent; i=j=0; lens=strlen(s); lent=strlen(t); while(i<lens&&j<lent){ if(j==-1||s[i]==t[j]){ ++i; ++j; } else j=_next[j]; } //if(j>=lent)return i-lent; //else return -1; return j; } int main(){ char str[26],str1[MAXN],str2[MAXN]; char cstr[26];//密文->明文 int T; scanf("%d",&T); while(T--){ scanf("%s",str); scanf("%s",str1); for(int i=0;i<26;++i){//密文->明文 cstr[str[i]-'a']='a'+i; } int len1=strlen(str1); for(int i=0;i<len1;++i){ str2[i]=cstr[str1[i]-'a']; } str2[len1]='\0'; GetNext(str2); int len11=len1/2;//假设串中明文长度(即串中明文最大长度) int num=KMPIndex(str1+len1-len11,str2);//串中实际明文长度 printf("%s",str1); len11=len1-num;//实际密文(明文)长度 for(int i=num;i<len11;++i){ printf("%c",str2[i]); } printf("\n"); } return 0; }
c2.扩展kmp(略)
Problem M.Substrings
d.找出所有串的最长的公共连续子串(逆序相同也可以)
s.直接从最小的那串,枚举所有子串去寻找。反正最多100串,最长100字符。
c.
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; int main(){ int t; int n; char str[105][105]; char subs1[105],subs2[105];//子串 int mi,lmi; int tmp; int len; bool flag; int ans; int len2; scanf("%d",&t); while(t--){ scanf("%d",&n); mi=105;//最小长度 lmi=0;//最小长度的下标 for(int i=0;i<n;++i){ scanf("%s",str[i]); tmp=strlen(str[i]); if(tmp<mi){ mi=tmp; lmi=i; } } len=strlen(str[lmi]);//最小的长度 ans=0; for(int i=0;i<len;++i){//子串头 for(int j=i;j<len;++j){//子串尾 for(int k=i;k<=j;++k){ subs1[k-i]=str[lmi][k];//正序 subs2[j-k]=str[lmi][k];//逆序 } subs1[j-i+1]='\0'; subs2[j-i+1]='\0'; len2=strlen(subs1); flag=true; for(int k=0;k<n;++k){ if(!strstr(str[k],subs1)&&!strstr(str[k],subs2)){ flag=false; break; } } if(flag&&len2>ans){ ans=len2; } } } printf("%d\n",ans); } return 0; }
Problem Z.Theme Section
d.在一个串中找 EAEBE 的形式的最长的E,其中E为一个字符串,也就是说找到前缀与后缀相同,并且串中还存在相同的一段,它们不能重复。
s.利用next数组,next[len]代表的即是最大的相同的前缀与后缀,然后让 i 从len-1往前遍历找到 i>=2(前面部分最少要有2个字符),在过程中更新最长的长度ans即可。
c.
#include<iostream> #include<stdio.h> #include<string.h> using namespace std; #define MAXN 1000005//字符串长度 char str[MAXN]; int _next[MAXN]; void GetNext(char t[]){//求next数组 int j,k,len; j=0;//从0开始,首先求_next[1] k=-1;//比较指针 _next[0]=-1;//初始值-1 len=strlen(t); while(j<len){ if(k==-1||t[j]==t[k]){//指针到头了,或者相等 ++j; ++k; _next[j]=k;//此句可由优化替代 /*优化(求匹配位置时可用) if(t[j]!=t[k])_next[j]=k; else _next[j]=_next[k]; //*/ } else k=_next[k]; } } int main(){ int N; int len; int m,k,ans;//m代表最大的首尾相同长度,k为i之前与开头重复的长度 scanf("%d",&N); while(N--){ scanf("%s",str); GetNext(str); len=strlen(str); m=_next[len];//m代表最大的首尾相同长度 ans=0; for(int i=len-1;i>=2;--i){ k=_next[i];//k为i之前与开头重复的长度 while(k>0){ if(k<=m&&k+k<=i&&i+k<=len){//长度小于m,且三段不重合 if(k>ans)ans=k; break;//当前是最大的长度 } k=_next[k];//next[k]一定小于k } } printf("%d\n",ans); } return 0; }
扩展KMP:
Manacher: