//kmp算法的主要作用在于对next数组的运用,所以这里只给出next数组的模板
//性质1:对于每一个长度len的子串,该子串的最小循环节为len-next[len]
//性质2:kmp的next不断向前递归的过程可以保证对于每一个当前前缀,都有一段后缀与之对应
#include
#include
#include
using namespace std ;
const int maxn = 1e6+5;
int Next[maxn];
char mo[maxn];
int n2;
void GetNext()
{
int i=0,j=-1;
while(iif(j==-1||mo[i]==mo[j]) {++i,++j,Next[i]=j;}
else j=Next[j];
}
return ;
}
int main()
{
scanf("%s",mo);
n2=strlen(mo);
Next[0]=-1;
GetNext();
return 0;
}
HDU1711
题意就是给你两个序列,让你求B序列在A序列第一次出现(完全相同)的下标
本题就是KMP的模板题,将i指针指向A串,将j指针指向B串,如果匹配就继续下一位的匹配,如果不匹配,将j跳转到next[j],继续向前匹配。
HDU1711代码
#include
#include
using namespace std ;
const int maxn = 1e6+5;
int Next[maxn];
int str[maxn];
int mo[maxn];
int n1,n2;
void GetNext()
{
int i=0,j=-1;
while(iif(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(iif(j==-1||str[i]==mo[j]) i++,j++;
else j=Next[j];//next数组寻找与当前后缀匹配最长的前缀,省略不必要的查找
if(j==n2)
return i-n2+1;//首地址
}
return -1;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n1,&n2);
for(int i=0;iscanf("%d",&str[i]);
for(int j=0;jscanf("%d",&mo[j]);
Next[0]=-1;
GetNext();
printf("%d\n",kmp());
}
return 0;
}
HDU1686
题意就是求B串在A串中的出现次数(可重叠
依旧是利用next数组,当某次匹配完成之后,将ans++,然后把j跳转到next[j],原理类似上一题的不匹配的情况,省略不必要的查找。
HDU1686代码
#include
#include
#include
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(iif(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(iif(j==-1||str[i]==mo[j]) i++,j++;
else j=Next[j];
if(j==n2)
{
cnt++;
j=Next[j];//完成一次匹配,将j移动到最长的前缀处,省略不必要的查找
}
}
return cnt;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%s%s",mo,str);
n1=strlen(str);
n2=strlen(mo);
Next[0]=-1;
GetNext();
printf("%d\n",kmp());
}
return 0;
}
HDU2087
题意就是求B串在A串中的出现次数(不可重叠)
做法和上题类似,只不过在每次完成匹配之后,要考虑不能重叠的问题,所以j不能跳转到next[j],而应该从0重新开始,因为A中已经匹配过的字符不能再利用,所以就变成了A剩下的子串中找B出现的次数,所以j应该从0重新开始匹配。
HDU2087代码
#include
#include
#include
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(iif(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(iif(j==-1||str[i]==mo[j]) i++,j++;
else j=Next[j];
if(j==n2)
{
cnt++;
j=0;
}
}
return cnt;
}
int main()
{
while(scanf("%s",str)!=EOF)
{
n1=strlen(str);
if(n1==1&&str[0]=='#') break;
scanf("%s",mo);
n2=strlen(mo);
Next[0]=-1;
GetNext();
printf("%d\n",kmp());
}
return 0;
}
HDU3746
本题题意为添加最少的字符使原字符串变成周期至少为2的循环字符串
用到模板里所说的,长度为len的字符串的最小循环节为len-next[len],求出最小循环节,算出最后应该补充多少就结束了。
求最小循环节的证明法请看此链接KMP求字符串最小循环节
HDU3746代码
#include
#include
#include
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(iif(j==-1||mo[i]==mo[j]) {++i,++j,Next[i]=j;}
else j=Next[j];
}
return ;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%s",mo);
n2=strlen(mo);
Next[0]=-1;
GetNext();
int tmp=n2-Next[n2];
if(n2%tmp==0&&n2!=tmp)
{
printf("0\n");
}
else
{
printf("%d\n",tmp-n2%tmp);
}
}
return 0;
}
HDU1358
本题题意为求给定字符串中所有为循环串的前缀,并输出该前缀的最后一个字符下标和周期
用到模板中给定的方法,只要对每个字符串判定一下是否为循环串就可以了
HDU1358代码
#include
#include
#include
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(iif(j==-1||mo[i]==mo[j]) {++i,++j,Next[i]=j;}
else j=Next[j];
}
return ;
}
int main()
{
int cnt=1;
while(scanf("%d",&n2)!=EOF)
{
if(n2==0) break;
scanf("%s",mo);
Next[0]=-1;
GetNext();
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
本题题意为求给定字符串的最大周期,做法类似上题,直接上代码
POJ2406代码
#include
#include
#include
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(iif(j==-1||mo[i]==mo[j]) {++i,++j,Next[i]=j;}
else j=Next[j];
}
return ;
}
int main()
{
int cnt=1;
while(scanf("%s",mo)!=EOF)
{
n2=strlen(mo);
if(n2==1&&mo[0]=='.') break;
Next[0]=-1;
GetNext();
int tmp=n2-Next[n2];
if(n2%tmp==0) printf("%d\n",n2/tmp);
else printf("1\n");
}
return 0;
}
POJ2752
本题题意为求出所有在后缀中出现过的前缀的最后一个元素的下标
本题要考虑一下next数组的本质,其实就是最长的出现在后缀中的前缀,但是由于本题要求所有的而不是最长的,考虑到next数组的递归过程,其实就是对每一个当前长度的前缀,都有完全相同的后缀与之对应,所以就不断递归next数组即可求解。
POJ2752代码
#include
#include
#include
#include
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(iif(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
#include
#include
#include
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;iscanf("%s",str);
str2[i]=str;
}
int ans=0;
string tmp=str2[0];
int tmplen=tmp.size();
for(int i=0;ifor(int j=1;i+j<=tmplen;j++)
{
int cnt=1;
string ss=tmp.substr(i,j);
for(int k=1;kif(str2[k].find(ss)!=-1)
cnt++;
}
if(cnt==n)
{
if(anselse if(ans==j)
{
ansstr=min(ansstr,ss);
}
}
}
}
if(ans<3) printf("no significant commonalities\n");
else printf("%s\n",ansstr.c_str());
}
}
POJ3080(后缀数组写法
//具体实现原理可以参考我的后缀数组博客
#include
#include
#include
#include
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; i0;
for(i=0; i<=n; i++) wsf[x[i]=r[i]]++;
for(i=1; i1];
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; i0;
for(i=0; i<=n; i++) wsf[wv[i]]++;
for(i=1; i1];
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; iif(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]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;iscanf("%s",str[i]);
lenn[i]=strlen(str[i]);
for(int j=0;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;iprintf("%c",s[i]-1+'A');
printf("\n");
}
}
return 0;
}
HDU2594
本题题意是求既是A串中的前缀又是B串中的后缀的最长长度。
如果我们将AB进行拼接,我们可以发现 next[len1+len2] n e x t [ l e n 1 + l e n 2 ] 即为最长的即使前缀又是后缀的子串,但是这里有一个细节,就是如果这个长度大于 min(len1,len2) m i n ( l e n 1 , l e n 2 ) ,代表这个是拼接之后产生的,是不可取的,所以我们可以运用性质2来不断减少这个长度,直到他满足 len<min(len1,len2) l e n < m i n ( l e n 1 , l e n 2 ) 即可。
HDU2594代码
#include
#include
#include
#include
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(iif(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'\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;iprintf("%c",mo[i]);
printf(" ");
printf("%d\n",ans);
}
return 0;
}
HDU3336
本题题意为求字符串的每个前缀在整个字符串中的出现次数。
我们想象性质二,如果next[j]对答案有一个贡献,那么这个贡献在j中一定会再贡献一次,而且j为结尾的字符串对于总串产生的贡献只有长度为j的子串,于是我们可以得到转移方程 ans[j]=ans[next[j]]+1 a n s [ j ] = a n s [ n e x t [ j ] ] + 1 ,最后对所有前缀的贡献取和即为答案。
HDU3336代码
#include
#include
#include
#include
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(iif(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(iif(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 >= l e n / 2 的,所以我们可以将后半段的字符均按照映射换为密文,然后找到最长的既在前缀中出现又在后缀中出现的子串,根据性质二和第九题的做法,不断递归next直到找到符合条件的 len l e n 然后跳出即可,输出时要注意明文密文的转换
HDU4300代码
#include
#include
#include
#include
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(iif(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'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;iprintf("%c",str2[i]);
for(int i=0;iprintf("%c",nn[str2[i]-'a']+'a');
printf("\n");
}
return 0;
}
HDU1238
多个字符串的最长公共子串,只不过子串可以逆置出现,只要把第八题的做法反过来再找一次就好,直接上代码
#include
#include
#include
#include
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;iscanf("%s",str);
str2[i]=str;
}
int ans=0;
string tmp=str2[0];
int tmplen=tmp.size();
for(int i=0;ifor(int j=1;i+j<=tmplen;j++)
{
int cnt=1;
string ss=tmp.substr(i,j);
for(int k=1;kif(str2[k].find(ss)!=-1)
cnt++;
}
if(cnt==n)
{
if(anselse if(ans==j)
{
ansstr=min(ansstr,ss);
}
}
}
}
reverse(tmp.begin(),tmp.end());
for(int i=0;ifor(int j=1;i+j<=tmplen;j++)
{
int cnt=1;
string ss=tmp.substr(i,j);
for(int k=1;kif(str2[k].find(ss)!=-1)
cnt++;
}
if(cnt==n)
{
// cout<
if(anselse if(ans==j)
{
ansstr=min(ansstr,ss);
}
}
}
}
printf("%d\n",ans);
}
}
HDU3374
本题题意为求一个字符串旋转后的所有串中字典序最大和字典序最小分别出现的次数。
搜先我们要了解字符串的最小表示法 o(n) o ( n ) 的时间复杂度求出旋转后字典序最小的起始下标。
最小表示法戳这里字符串最小表示法
了解了最小表示法之后,我们考虑一下,会发现,只有字符串有循环节的时候才会出现旋转后有相同的串出现的情况,所以我们利用性质1判断是否字符串存在循环节,然后利用最小/最大表示法分别求出下标即可
HDU3374代码
#include
#include
#include
#include
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(iif(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(is[(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 iint 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
#include
#include
#include
#include
using namespace std;
int getmin(char *s)
{
int n=strlen(s);
int i=0,j=1,k=0,t;
while(iif (!t) k++;
else
{
if (t>0) i+=k+1;
else j+=k+1;
if (i==j) j++;
k=0;
}
}
return ichar str[105];
string tmp;
set<string> s;
int main()
{
int n;
while(scanf("%d",&n)!=EOF)
{
s.clear();
for(int i=0;iscanf("%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
本题题意为求一个长度 P P ,使所有的
#include
#include
#include
#include
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(iif(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;iprintf("%d ",n2-ans[i]);
printf("%d\n",n2);
}
return 0;
}
UVA11475
本题题意是给你一个字符串,求出最少拼接字符数使其变为回文串
通过思考我们可以发现,只有作为后缀的回文串对结果产生贡献,那么这个题就转变为了找最长的后缀回文子串,我们将字符串倒置再拼接上原串,求得的next[len]也就是倒置字符串的前缀与原字符串的后缀的最大匹配长度,也就是本题答案。
UVA11475代码
#include
#include
#include
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(iif(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//拼接
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;
}
未完待续…