KMP模板
kmp算法的主要作用在于对next数组的运用,所以这里只给出next数组的模板
性质1:对于每一个长度len的子串,该子串的最小循环节为len-next[len]
性质2:kmp的next不断向前递归的过程可以保证对于每一个当前前缀,都有一段后缀与之对应
#include<iostream>
#include<cstring>
#define maxn 100010
using namespace std;
int next[maxn];
char s[maxn];
char p[maxn];
void prekmp(char x[],int m,int Next [])
{
int i,j;
j = Next[0] = -1;
i = 0;
while(i<m){
while(j!=-1&&x[i]!=x[j]) j = Next[j];
Next[++i] = ++j;
}
}
//计数 可重复
int kmpcount(char pp[],int m,char ss[],int n)
{
int ans = 0;
int i,j;
i = j = 0;
prekmp(pp,m,next);
while(i<n){
while(j!=-1&&ss[i]!=pp[j]) j = next[j];
i++;
j++;
if(j>=m){
ans++;
j = next[j];
}
}
return ans;
}
//计数 不可重复
int kmpcount(char pp[],int m,char ss[],int n)
{
int ans = 0;
int i,j;
i = j = 0;
prekmp(pp,m,next);
while(i<n){
while(j!=-1&&ss[i]!=pp[j]) j = next[j];
i++;
j++;
if(j>=m){
ans++;
j = 0;
}
}
return ans;
}
//返回位置
int kmp(char pp[],int m,char ss[],int n)
{
int ans = 0;
int i,j;
i = j = 0;
prekmp(pp,m,next);
while(i<n){
while(j!=-1&&ss[i]!=pp[j]) j = next[j];
i++;
j++;
if(j>=m){
ans = i-j;
return ans;
}
}
return -1;
}
基础题
HDU1711
题意就是给你两个序列,让你求B序列在A序列第一次出现(完全相同)的下标
这道题是一道板子题,直接用返回位置的那个板子贴上就过了,不过还是要注意细节问题,输入输出尽量用printf、scanf,不然会tle,开数组卡着边界开,不然会mle,另外注意输入输出格式 ,不然会pe。代码在下面?
#include<iostream>
#include<cstring>
#include<stdio.h>
const int maxn = 1e6+5;
using namespace std;
int NEXT[maxn];
int pat[maxn];
int str[maxn];
void prekmp(int x[],int m,int Next[])
{
int i,j;
j = Next[0] = -1;
i = 0;
while(i<m){
while(j!=-1&&x[i]!=x[j]) j = Next[j];
Next[++i] = ++j;
}
}
int kmp (int p[],int m,int s[],int n)
{
int i,j;
int ans = 0;
i = j = 0;
prekmp(p,m,NEXT);
while(i<n){
while(j!=-1&&p[j]!=s[i]) j = NEXT[j];
i++;
j++;
if(j>=m){
ans = i-m+1;
return ans;
}
}
return -1;
}
int main()
{
int t = 0;
cin>>t;
for(int i=0;i<t;i++)
{
int n,m;
scanf("%d%d",&n,&m);
for(int j=0;j<n;j++){
scanf("%d",&str[j]);
}
for(int j=0;j<m;j++){
scanf("%d",&pat[j]);
}
int ans = kmp(pat,m,str,n);
printf("%d\n",ans);
}
return 0;
}
HDU1686
题意就是求B串在A串中的出现次数(可重叠 )
这道题同样是板子题,用计数可重复那个板子,主要是可以做做这道题熟悉一下板子。
还有就是板子题,大家背板子的时候一定要背对了啊,尤其是条件。
#include<iostream>
#include<cstring>
using namespace std;
const int maxn = 1e4+5;
int NEXT[maxn];
char pat[maxn];
char str[1000005];
void prekmp(char x[],int m,int Next [])
{
int i,j;
j = Next[0] = -1;
i = 0;
while(i<m){
while(j!=-1&&x[i]!=x[j]) j = Next[j];
Next[++i] = ++j;
}
}
int kmpcount(char p[],int m,char s[],int n)
{
int ans = 0;
int i,j;
i = j = 0;
prekmp(p,m,NEXT);
while(i<n){
while(j!=-1&&s[i]!=p[j]) j = NEXT[j];
i++;
j++;
if(j>=m){
ans++;
j = NEXT[j];
//j = 0;
}
}
return ans;
}
int main()
{
int t;
cin>>t;
for(int i=0;i<t;i++){
cin>>pat;
cin>>str;
int m = strlen(pat);
int n = strlen(str);
int ans = kmpcount(pat,m,str,n);
cout<<ans<<endl;
}
return 0;
}
HDU2087
题意是主串能切出几个子串,言下之意是不能重复
这道题还是一个板子题,直接贴那个计数不重复那个就过了。
需要注意的是输入方法。
#include<iostream>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn = 1e5+5;
int NEXT[maxn];
char pat[maxn];
char str[1000005];
void prekmp(char x[],int m, int Next[])
{
int i,j;
j = Next[0] = -1;
i = 0;
while(i<m){
while(j!=-1&&x[i]!=x[j]) j = Next[j];
Next[++i] = ++j;
}
}
int kmp(char p[],int m,char s[],int n)
{
int i,j;
int ans = 0;
i = j = 0;
prekmp(p,m,NEXT);
while(i<n){
while(j!=-1&&p[j]!=s[i]) j = NEXT[j];
i++;
j++;
if(j>=m){
ans++;
j = 0;
}
}
return ans;
}
int main()
{
while(scanf("%s",str)!=EOF)
{
int n = strlen(str);
if(n==1&&str[0]=='#') break;
scanf("%s",pat);
int m = strlen(pat);
printf("%d\n",kmp(pat,m,str,n));
}
return 0;
}
到现在应该板子很熟了,下面练一下next数组的性质。
HDU3746
题意为添加最少的字符使原字符串变成周期至少为2的循环字符串
这道题利用了next数组的特性,结论是长度为m的pattern串的循环结长度为m-next[m]
代码如下:
#include<iostream>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn = 100005;
int NEXT[maxn];
char pat[maxn];
void prekmp(char x[],int m,int Next[])
{
int i,j;
j = Next[0] = -1;
i = 0;
while(i<m){
while(j!=-1&&x[i]!=x[j]) j = Next[j];
Next[++i] = ++j;
}
}
int kmp(char p[], int m)
{
int ans = 0;
prekmp(p,m,NEXT);
ans = m - NEXT[m];///最小循环结
return ans;
}
int main()
{
int t;
cin>>t;
for(int i=0;i<t;i++){
char pat[maxn];
scanf("%s",&pat);
int m = strlen(pat);
int ans = kmp(pat,m);
if(m%ans==0&&m!=ans){
printf("0\n");
}
else{
int x = m%ans;
printf("%d\n",ans-x);
}
}
return 0;
}
HDU1358
这道题和上题类似同样用到next数组与最小循环结的应用,m-next[m]
代码如下:
#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std ;
const int maxn = 1e6+5;
int NEXT[maxn];
char str[maxn];
char mo[maxn];
int n1,n2;
void prekmp(char x[],int m,int Next[])
{
int i,j;
j = Next[0] = -1;
i = 0;
while(i<m){
while(j!=-1&&x[i]!=x[j]) j = Next[j];
Next[++i] = ++j;
}
}
int main()
{
int cnt=1;
while(scanf("%d",&n2)!=EOF)
{
if(n2==0) break;
scanf("%s",mo);
NEXT[0]=-1;
prekmp(mo,n2,NEXT);
printf("Test case #%d\n",cnt++);
for(int i=1;i<=n2;i++)
{
int tmp=i-NEXT[i];
if(i/tmp==1) continue;
if(i%tmp==0)
{
printf("%d %d\n",i,i/tmp);
}
}
printf("\n");
}
return 0;
}
POJ2406
题意是给定字符串,输出循环周期。
这道题还是用到了next数组循环结那个性质。
代码如下:
#include<iostream>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn = 1000005;
int NEXT[maxn];
char pat[maxn];
void kmp(char x[],int m,int Next[])
{
int i,j;
j = Next[0] = -1;
i = 0;
while(i<m){
while(-1!=j&&x[i]!=x[j]) j = Next[j];
Next[++i] = ++j;
}
return;
}
int main()
{
while(scanf("%s",pat)){
if(strcmp(pat,".")==0)
break;
int m = strlen(pat);
kmp(pat,m,NEXT);
int l = m-NEXT[m];
if(m%l==0)
printf("%d\n",m/l);
else
printf("1\n");
}
return 0;
}
POJ2752
本题题意为求出所有在后缀中出现过的前缀的最后一个元素的下标
本题要考虑一下next数组的本质,其实就是最长的出现在后缀中的前缀,但是由于本题要求所有的而不是最长的,考虑到next数组的递归过程,其实就是对每一个当前长度的前缀,都有完全相同的后缀与之对应,所以就不断递归next数组即可求解。
POJ2752代码
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<stack>
using namespace std ;
const int maxn = 1e6+5;
int ans[maxn];
int Next[maxn];
char str[maxn];
char mo[maxn];
int n1,n2;
void GetNext()
{
int i=0,j=-1;
while(i<n2)
{
if(j==-1||mo[i]==mo[j]) {++i,++j,Next[i]=j;}
else j=Next[j];
}
return ;
}
int main()
{
int cnt;
while(scanf("%s",mo)!=EOF)
{
cnt=0;
n2=strlen(mo);
Next[0]=-1;
GetNext();
int j=n2;
while(j!=0)
{
ans[cnt++]=j;
j=Next[j];
}
for(int i=cnt-1;i>=0;i--)
{
printf("%d%c",ans[i],i==0?'\n':' ');
}
}
return 0;
}
POJ3080
本题题意为求m个字符串长度至少为3的最长公共子串
由于m只有10而且len小于60,我们可以选择枚举某一个串的子串并用str.find()或者kmp验证是否所有该子串在所有字符串中出现过,也可以用经典的二分长度将height数组分块的后缀数组做法
POJ3080(find解法
//由于只查找是否出现过,算法复杂度差距不大,所以这里给出简单一些的写法
#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<string.h>
using namespace std;
const int maxn = 65;
char str[maxn];
string ansstr;
string str2[maxn];
int main()
{
int n,t;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%s",str);
str2[i]=str;
}
int ans=0;
string tmp=str2[0];
int tmplen=tmp.size();
for(int i=0;i<tmplen;i++)
{
for(int j=1;i+j<=tmplen;j++)
{
int cnt=1;
string ss=tmp.substr(i,j);
for(int k=1;k<n;k++)
{
if(str2[k].find(ss)!=-1)
cnt++;
}
if(cnt==n)
{
if(ans<j)
{
ans=j;
ansstr=ss;
}
else if(ans==j)
{
ansstr=min(ansstr,ss);
}
}
}
}
if(ans<3) printf("no significant commonalities\n");
else printf("%s\n",ansstr.c_str());
}
}
POJ3080(后缀数组写法
//具体实现原理可以参考我的后缀数组博客
#include <iostream>
#include<algorithm>
#include <stdio.h>
#include <string.h>
using namespace std;
#define maxn 4005
const int INF = 0x3f3f3f3f;
int wa[maxn],wb[maxn],wsf[maxn],wv[maxn],sa[maxn];
int rank[maxn],height[maxn],s[maxn];
char str[15][65];
int t,lenn[maxn];
int belong[maxn];
int anspos;
int vis[65];
int cmp(int *r,int a,int b,int k)
{
return r[a]==r[b]&&r[a+k]==r[b+k];
}
void getsa(int *r,int *sa,int n,int m)
{
int i,j,p,*x=wa,*y=wb,*t;
for(i=0; i<m; i++) wsf[i]=0;
for(i=0; i<=n; i++) wsf[x[i]=r[i]]++;
for(i=1; i<m; i++) wsf[i]+=wsf[i-1];
for(i=n; i>=0; i--) sa[--wsf[x[i]]]=i;
p=1;
j=1;
for(; p<=n; j*=2,m=p)
{
for(p=0,i=n+1-j; i<=n; i++) y[p++]=i;
for(i=0; i<=n; i++) if(sa[i]>=j) y[p++]=sa[i]-j;
for(i=0; i<=n; i++) wv[i]=x[y[i]];
for(i=0; i<m; i++) wsf[i]=0;
for(i=0; i<=n; i++) wsf[wv[i]]++;
for(i=1; i<m; i++) wsf[i]+=wsf[i-1];
for(i=n; i>=0; i--) sa[--wsf[wv[i]]]=y[i];
t=x;
x=y;
y=t;
x[sa[0]]=0;
for(p=1,i=1; i<=n; i++)
x[sa[i]]=cmp(y,sa[i-1],sa[i],j)? p-1:p++;
}
}
void getheight(int *r,int n)
{
int i,j,k=0;
for(i=1; i<=n; i++) rank[sa[i]]=i;
for(i=0; i<n; i++)
{
if(k)
k--;
else
k=0;
j=sa[rank[i]-1];
while(r[i+k]==r[j+k])
k++;
height[rank[i]]=k;
}
}
int check(int x,int n)
{
for(int i=1;i<=n-1;i++)
{
if(height[i]<x) continue;
int cnt=0;
for(int j=0;j<=t;j++) vis[j]=0;
while(height[i]>=x&&i<=n-1)
{
if(!vis[belong[sa[i-1]]])
{
vis[belong[sa[i-1]]]=1;
cnt++;
}
i++;
}
if(!vis[belong[sa[i-1]]])
{
vis[belong[sa[i-1]]]=1;
cnt++;
}
if(cnt>=t)
{
anspos=sa[i-1];
return true;
}
}
return false;
}
int main()
{
int len,n;
int casee;
scanf("%d",&casee);
while(casee--)
{
scanf("%d",&t);
if(t==0) break;
n=0;
int pos=30;
for(int i=0;i<t;i++)
{
scanf("%s",str[i]);
lenn[i]=strlen(str[i]);
for(int j=0;j<lenn[i];j++)
{
s[n++]=str[i][j]-'A'+1;
belong[n-1]=i;
}
s[n++]=pos++;
}
s[n]=0;
getsa(s,sa,n,pos);
getheight(s,n);
int l=1,r=60,mid;
while(l<=r)
{
mid=(l+r)>>1;
if(check(mid,n)) l=mid+1;
else r=mid-1;
}
if(r<3) printf("no significant commonalities \n");
else
{
for(int i=anspos;i<anspos+r;i++)
printf("%c",s[i]-1+'A');
printf("\n");
}
}
return 0;
}
HDU2594
本题题意是求既是A串中的前缀又是B串中的后缀的最长长度。
如果我们将AB进行拼接,我们可以发现next[len1+len2]next[len1+len2]即为最长的即使前缀又是后缀的子串,但是这里有一个细节,就是如果这个长度大于min(len1,len2)min(len1,len2),代表这个是拼接之后产生的,是不可取的,所以我们可以运用性质2来不断减少这个长度,直到他满足len<min(len1,len2)len<min(len1,len2)即可。
HDU2594代码
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<stack>
using namespace std ;
const int maxn = 1e6+5;
int Next[maxn];
char str[maxn];
char mo[maxn];
int n1,n2;
void GetNext()
{
int i=0,j=-1;
while(i<n2)
{
if(j==-1||mo[i]==mo[j]) {++i,++j,Next[i]=j;}
else j=Next[j];
}
return ;
}
int main()
{
int cnt;
while(scanf("%s%s",mo,str)!=EOF)
{
n1=strlen(str);
n2=strlen(mo);
int tmp=n2;
for(int i=n2;i<n2+n1;i++)
{
mo[i]=str[i-n2];
}
n2=n1+n2;
mo[n2]='\0';
Next[0]=-1;
GetNext();
int ans=Next[n2];
while(ans>min(tmp,n2-tmp))
{
ans=Next[ans];
}
if(ans==0)
{
printf("0\n");
continue;
}
for(int i=0;i<ans;i++)
printf("%c",mo[i]);
printf(" ");
printf("%d\n",ans);
}
return 0;
}
HDU3336
本题题意为求字符串的每个前缀在整个字符串中的出现次数。
我们想象性质二,如果next[j]对答案有一个贡献,那么这个贡献在j中一定会再贡献一次,而且j为结尾的字符串对于总串产生的贡献只有长度为j的子串,于是我们可以得到转移方程ans[j]=ans[next[j]]+1ans[j]=ans[next[j]]+1,最后对所有前缀的贡献取和即为答案。
HDU3336代码
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<stack>
using namespace std ;
const int maxn = 1e6+5;
int Next[maxn];
char str[maxn];
char mo[maxn];
int dp[maxn];
int n1,n2;
void GetNext()
{
int i=0,j=-1;
while(i<n2)
{
if(j==-1||mo[i]==mo[j]) {++i,++j,Next[i]=j;}
else j=Next[j];
}
return ;
}
int kmp()
{
int cnt=0;
int i=0,j=0;
while(i<n1)
{
if(j==-1||str[i]==mo[j]) i++,j++;
else j=Next[j];
if(j==n2)
{
cnt++;
j=0;
}
}
return cnt;
}
int ans[maxn];
int main()
{
int cnt;
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%s",&n2,mo);
Next[0]=-1;
GetNext();
int ans=0;
dp[0]=0;
for(int i=1;i<=n2;i++)
{
dp[i]=dp[Next[i]]+1;
ans=(ans+dp[i])%10007;
}
printf("%d\n",ans);
}
return 0;
}
HDU4300
本题题意比较难读懂,题意为给你一段密文的映射方式和一段密文+明文的字符串,密文是完整的,而明文不一定是完整的,让你添加最少的字符使他变为完整的密文+明文
如果读懂题意,可以考虑给定字符串中密文长度一定是>=len/2>=len/2的,所以我们可以将后半段的字符均按照映射换为密文,然后找到最长的既在前缀中出现又在后缀中出现的子串,根据性质二和第九题的做法,不断递归next直到找到符合条件的lenlen然后跳出即可,输出时要注意明文密文的转换
HDU4300代码
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<stack>
using namespace std ;
const int maxn = 1e5+5;
int Next[maxn];
char str[maxn];
char str2[maxn];
char mo[maxn];
int dp[maxn];
int mm[30];
int nn[maxn];
int n1,n2;
void GetNext()
{
int i=0,j=-1;
while(i<n2)
{
if(j==-1||mo[i]==mo[j]) {++i,++j,Next[i]=j;}
else j=Next[j];
}
return ;
}
int ans[maxn];
int main()
{
int cnt;
int t;
scanf("%d",&t);
while(t--)
{
scanf("%s%s",str,mo);
strcpy(str2,mo);
n2=strlen(mo);
n1=strlen(str);
for(int i=0;i<n1;i++)
{
mm[i]=str[i]-'a';
nn[str[i]-'a']=i;
}
for(int i=0;i<(n2+1)/2;i++)
{
mo[i]=nn[mo[i]-'a']+'a';
}
Next[0]=-1;
GetNext();
int ans=Next[n2];
while(ans>min((n2+1)/2,n2-(n2+1)/2))
{
ans=Next[ans];
}
for(int i=0;i<n2-ans;i++)
printf("%c",str2[i]);
for(int i=0;i<n2-ans;i++)
printf("%c",nn[str2[i]-'a']+'a');
printf("\n");
}
return 0;
}
HDU1238
多个字符串的最长公共子串,只不过子串可以逆置出现,只要把第八题的做法反过来再找一次就好,直接上代码
#include<stdio.h>
#include<algorithm>
#include<iostream>
#include<string.h>
using namespace std;
const int maxn = 105;
char str[maxn];
string ansstr;
string str2[maxn];
int main()
{
int n,t;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=0;i<n;i++)
{
scanf("%s",str);
str2[i]=str;
}
int ans=0;
string tmp=str2[0];
int tmplen=tmp.size();
for(int i=0;i<tmplen;i++)
{
for(int j=1;i+j<=tmplen;j++)
{
int cnt=1;
string ss=tmp.substr(i,j);
for(int k=1;k<n;k++)
{
if(str2[k].find(ss)!=-1)
cnt++;
}
if(cnt==n)
{
if(ans<j)
{
ans=j;
ansstr=ss;
}
else if(ans==j)
{
ansstr=min(ansstr,ss);
}
}
}
}
reverse(tmp.begin(),tmp.end());
for(int i=0;i<tmplen;i++)
{
for(int j=1;i+j<=tmplen;j++)
{
int cnt=1;
string ss=tmp.substr(i,j);
for(int k=1;k<n;k++)
{
if(str2[k].find(ss)!=-1)
cnt++;
}
if(cnt==n)
{
// cout<<ss<<" "<<cnt<<endl;
if(ans<j)
{
ans=j;
ansstr=ss;
}
else if(ans==j)
{
ansstr=min(ansstr,ss);
}
}
}
}
printf("%d\n",ans);
}
}
HDU3374
本题题意为求一个字符串旋转后的所有串中字典序最大和字典序最小分别出现的次数。
搜先我们要了解字符串的最小表示法o(n)o(n)的时间复杂度求出旋转后字典序最小的起始下标。
最小表示法戳这里字符串最小表示法
了解了最小表示法之后,我们考虑一下,会发现,只有字符串有循环节的时候才会出现旋转后有相同的串出现的情况,所以我们利用性质1判断是否字符串存在循环节,然后利用最小/最大表示法分别求出下标即可
HDU3374代码
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<stack>
using namespace std ;
const int maxn = 1e6+5;
int Next[maxn];
char mo[maxn];
int n1,n2;
void GetNext()
{
int i=0,j=-1;
while(i<n2)
{
if(j==-1||mo[i]==mo[j]) {++i,++j,Next[i]=j;}
else j=Next[j];
}
return ;
}
int getmin(char *s)
{
int n=strlen(s);
int i=0,j=1,k=0,t;
while(i<n && j<n && k<n)
{
t=s[(i+k)%n]-s[(j+k)%n];
if (!t) k++;
else
{
if (t>0) i+=k+1;
else j+=k+1;
if (i==j) j++;
k=0;
}
}
return i<j?i:j;
}
int getmax(char *s)
{
int len = strlen(s);
int i = 0, j = 1, k = 0;
while(i < len && j < len && k < len)
{
int t = s[(i+k)%len]-s[(j+k)%len];
if(!t) k++;
else
{
if(t > 0)
{
if(j+k+1 > i) j = j+k+1;
else j = i+1;
}
else if(i+k+1 > j) i = i+k+1;
else i = j+1;
k = 0;
}
}
return i < j ? i : j;
}
int ans[maxn];
int main()
{
int cnt;
while(scanf("%s",mo)!=EOF)
{
n2=strlen(mo);
Next[0]=-1;
GetNext();
int pos=getmin(mo);
int pos2=getmax(mo);
int tmp=n2-Next[n2];
if(n2%tmp==0)
{
printf("%d %d %d %d\n",pos+1,n2/tmp,pos2+1,n2/tmp);
}
else
{
printf("%d %d %d %d\n",pos+1,1,pos2+1,1);
}
}
return 0;
}
HDU2609
本题为给你n个01串,可以对每个串旋转任意次,求最少出现多少个不同的字符串,我们可以知道,如果两个字符串是可以旋转之后相同的,那么他们的最小表示法一定是相同的,所以我们可以求出所有字符串的最小表示法,然后用一个set去重就好了。
HDU2609代码
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
#include<set>
using namespace std;
int getmin(char *s)
{
int n=strlen(s);
int i=0,j=1,k=0,t;
while(i<n && j<n && k<n)
{
t=s[(i+k)%n]-s[(j+k)%n];
if (!t) k++;
else
{
if (t>0) i+=k+1;
else j+=k+1;
if (i==j) j++;
k=0;
}
}
return i<j?i:j;
}
char str[105];
string tmp;
set<string> s;
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
s.clear();
for(int i=0;i<n;i++)
{
scanf("%s",str);
tmp=str;
int pos=getmin(str);
string pp=tmp.substr(pos)+tmp.substr(0,pos);
s.insert(pp);
}
printf("%d\n",(int)s.size());
}
return 0;
}
FZU1901
本题题意为求一个长度PP,使所有的
iϵ[0,size(s)−p−1]iϵ[0,size(s)−p−1]
满足
S[i]=S[i+P]S[i]=S[i+P]
本题即为性质2的终极运用,在次重复一遍,kmp的next不断向前递归的过程可以保证对于每一个当前前缀,都有一段后缀与之对应,所以对于每一个next,我们都可以将他与最后的后缀对应作为一组答案。
FZU1901代码
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<stack>
using namespace std ;
const int maxn = 1e6+5;
int Next[maxn];
char mo[maxn];
int n1,n2;
void GetNext()
{
int i=0,j=-1;
while(i<n2)
{
if(j==-1||mo[i]==mo[j]) {++i,++j,Next[i]=j;}
else j=Next[j];
}
return ;
}
int ans[maxn];
int main()
{
int cnt=1;
int t;
scanf("%d",&t);
while(t--)
{
scanf("%s",mo);
n2=strlen(mo);
Next[0]=-1;
int pp=0;
GetNext();
int tmp=Next[n2];
while(tmp!=0)
{
ans[pp++]=tmp;
tmp=Next[tmp];
}
printf("Case #%d: %d\n",cnt++,pp+1);
for(int i=0;i<pp;i++)
printf("%d ",n2-ans[i]);
printf("%d\n",n2);
}
return 0;
}
UVA11475
本题题意是给你一个字符串,求出最少拼接字符数使其变为回文串
通过思考我们可以发现,只有作为后缀的回文串对结果产生贡献,那么这个题就转变为了找最长的后缀回文子串,我们将字符串倒置再拼接上原串,求得的next[len]也就是倒置字符串的前缀与原字符串的后缀的最大匹配长度,也就是本题答案。
UVA11475代码
#include<stdio.h>
#include<iostream>
#include<string.h>
using namespace std ;
const int maxn = 1e6+5;
int Next[maxn];
char mo[maxn];
char str[maxn];
int n2;
void GetNext()
{
int i=0,j=-1;
while(i<n2)
{
if(j==-1||mo[i]==mo[j]) {++i,++j,Next[i]=j;}
else j=Next[j];
}
return ;
}
int main()
{
while(scanf("%s",str)!=EOF)
{
n2=0;
int len=strlen(str);
for(int i=len-1;i>=0;i--)//字符串倒置
mo[n2++]=str[i];
mo[n2++]='#';//拼接符
for(int i=0;i<len;i++)
mo[n2++]=str[i];//拼接
mo[n2]='\0';
Next[0]=-1;
GetNext();
int tmp=Next[n2];
printf("%s",str);
for(int i=len-1-tmp;i>=0;i--) printf("%c",str[i]);
printf("\n");
}
return 0;
}
以上是kmp的板子题可以用来熟悉板子。
未完待续。。。