不錯的博客
求next數組:
void GetNext(char* p,int next[])
{
int pLen = strlen(p);
next[0] = -1;
int j = 0;
int k =next[j];
while (j < pLen) //最后,next[plen]也會求出來
{
//p[k]表示前綴,p[j]表示后綴
if (k == -1 || p[j] == p[k])
{
++k;
++j;
next[j] = k;
}
else
{
k = next[k];
}
}
}
注意:求next數組得到的最長公共前綴后綴是可以重疊的:
比如:ababa,next[5]=3,前綴為aba,后綴為aba
求next數組的方法可以優化:
void GetNextval(char* p, int next[])
{
int pLen = strlen(p);
int j = 0;
next[j] = -1;
int k = next[j];
while (j < pLen)
{
//p[k]表示前綴,p[j]表示后綴
if (k == -1 || p[j] == p[k])
{
++j;
++k;
//較之前next數組求法,改動在下面4行
if (p[j] != p[k])
next[j] = k; //之前只有這一行
else
//因為不能出現p[j] = p[ next[j ]],所以當出現時需要繼續遞歸,
//k = next[k] = next[next[k]]
next[j] = next[k];
}
else
{
k = next[k];
}
}
}
不同之處:
沒有優化的getnext函數,next數組存的是前綴和后綴的最大匹配值,而優化后的getnext函數存的是在這個基礎,進行更高效的改進。
比如abcabca
改進前最后一個字符next[7]=4,表示的是前綴和后綴最大匹配是4,即abca和abca。
改進后的next[7]=-1。這點也需要徹底搞懂,才能靈活的運用next函數。
總結一下:
在求前綴和后綴的最大匹配值時,要使用第一種,也就是未優化的算法。在運用KMP匹配字符串時,使用第二種算法,因為避免了多余的判斷,更加高效。
hihoCoder #1015
題意:
求模式串在字符串中出現的次數
題解:
#include<cstdio>
#include<cstring>
using namespace std;
char pattern[10010];
int next[10010];
char str[1000010];
void getNext()
{
int len=strlen(pattern);
int j=0;
next[j]=-1;
int k=next[j];
while(j<len)
{
if(k==-1||pattern[j]==pattern[k])
{
++j;++k;
if(pattern[j]!=pattern[k]) next[j]=k;
else next[j]=next[k];
}
else k=next[k];
}
}
int getCnt()
{
int lens=strlen(str);
int lenp=strlen(pattern);
getNext();
int sum=0;
int i=0,j=0;
while(i<lens)
{
if(j==-1||str[i]==pattern[j])
{
j++;
i++;
}
else j=next[j];
if(j==lenp) sum++;
}
return sum;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%s%s",pattern,str);
printf("%d\n",getCnt());
}
return 0;
}
Number Sequence
題意:
求模式串在字符串首次出現的位置(題目要求下標從1開始)
#include<cstdio>
#include<cstring>
using namespace std;
int p[10010],num[1000010],next[10010],n,m;
void getNext()
{
int j=0;
next[j]=-1;
int k=next[j];
while(j<m)
{
if(k==-1||p[j]==p[k])
{
j++;
k++;
if(p[j]!=p[k]) next[j]=k;
else next[j]=next[k];
}
else k=next[k];
}
}
int getIndex()
{
getNext();
int i=0,j=0,idx=-2;
while(i<n)
{
if(j==-1||num[i]==p[j])
{
i++;j++;
}
else j=next[j];
if(j==m)
{
idx=i-j;
break;
}
}
return idx;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++) scanf("%d",&num[i]);
for(int i=0;i<m;i++) scanf("%d",&p[i]);
printf("%d\n",getIndex()+1);
}
return 0;
}
剪花布條
題意:
求模式串在字符串中的不重疊的出現次數
題解:
kmp匹配時,如果發現j==plen,那么j賦為0
#include<cstdio>
#include<cstring>
using namespace std;
char str[1010],p[1010];
int next[1010],slen,plen;
void getNext()
{
int j=0;
next[j]=-1;
int k=next[j];
while(j<plen)
{
if(k==-1||p[j]==p[k])
{
k++;
j++;
if(p[j]!=p[k]) next[j]=k;
else next[j]=next[k];
}
else k=next[k];
}
}
int getUnReptitionCnt()
{
getNext();
int i=0,j=0,sum=0;
while(i<slen)
{
if(j==-1||str[i]==p[j])
{
i++;
j++;
}
else j=next[j];
if(j==plen)
{
sum++;
j=0;
}
}
return sum;
}
int main()
{
while(scanf("%s",str)!=EOF)
{
slen=strlen(str);
if(str[0]=='#'&&slen==1) break;
scanf("%s",p);
plen=strlen(p);
printf("%d\n",getUnReptitionCnt());
}
return 0;
}
Cyclic Nacklace
題意:
求字符串最小循環節
(最小循環節是不可重疊的:比如ababa:最小循環節是ab,而不是aba)
題解:
注意:求最小循環節時不可以用優化的方法求next數組
#include<cstdio>
#include<cstring>
using namespace std;
char p[100010];
int plen,next[100010];
void getNext()//求最小循環節時不可以用優化的方法求next數組
{
int j=0;
next[j]=-1;
int k=next[j];
while(j<plen)
{
if(k==-1||p[j]==p[k])
{
j++;
k++;
next[j]=k;
}
else k=next[k];
}
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%s",p);
plen=strlen(p);
getNext();
int minRepetend=plen-next[plen];//最小循環節長度
if(minRepetend!=plen&&plen%minRepetend==0) printf("0\n");
else printf("%d\n",minRepetend-plen%minRepetend);
}
return 0;
}
Period
題意:
給出一個字符串,如果它的某個前綴可以表示為循環k次的串,求出前綴index和k(k>1)
題解:
如果i%(i-next[i])==0,那么這個[0,i-1]的字符串可以表示為循環字符串
#include<cstdio>
#include<cstring>
using namespace std;
char p[1000010];
int plen,next[1000010];
void getNext()//求最小循環節時不可以用優化的方法求next數組
{
int j=0;
next[j]=-1;
int k=next[j];
while(j<plen)
{
if(k==-1||p[j]==p[k])
{
j++;
k++;
next[j]=k;
}
else k=next[k];
}
}
void solve()
{
int i=2;
getNext();
while(i<=plen)
{
if(next[i]!=0&&(i%(i-next[i])==0))
{
printf("%d %d\n",i,i/(i-next[i]));
}
i++;
}
printf("\n");
}
int main()
{
int cas=1;
while(scanf("%d",&plen)!=EOF,plen)
{
scanf("%s",p);
printf("Test case #%d\n",cas++);
solve();
}
return 0;
}
親和串
題意:
親和串的定義是這樣的:給定兩個字符串s1和s2,如果能通過s1循環移位,使s2包含在s1中,那么我們就說s2 是s1的親和串。
題解:
將s1擴大一倍,即把s1的字符復制一遍,然后判斷s2是不是在s1中。當然,如果s1原來的大小小于s2,那s1通過循環移位也不能包含s2
#include<cstdio>
#include<cstring>
using namespace std;
char str[200010];
char p[100010];
int next[100000];
int plen,slen;
void getNext()
{
int j=0;
next[j]=-1;
int k=next[j];
while(j<plen)
{
if(k==-1||p[j]==p[k])
{
j++;
k++;
if(p[j]!=p[k]) next[j]=k;
else next[j]=next[k];
}
else k=next[k];
}
}
bool contains()
{
int i=0,j=0;
while(i<slen)
{
if(j==-1||str[i]==p[j])
{
i++;
j++;
}
else j=next[j];
if(j==plen) return true;
}
return false;
}
int main()
{
while(scanf("%s",str)!=EOF)
{
slen=strlen(str);
scanf("%s",p);
plen=strlen(p);
if(plen>slen)
{
printf("no\n");
continue;
}
for(int i=0;i<slen;i++)
{
str[i+slen]=str[i];
}
slen<<=1;
str[slen]=0;
getNext();
if(contains()) printf("yes\n");
else printf("no\n");
}
return 0;
}
The Minimum Length
求最小循環節...不貼代碼了
Power Strings
題意:
也是求最小循環節了
題解:
#include<cstdio>
#include<cstring>
using namespace std;
char p[1000010];
int next[1000010];
int plen;
void getNext()
{
int j=0;
next[j]=-1;
int k=next[j];
while(j<plen)
{
if(k==-1||p[k]==p[j])
{
next[++j]=++k;
}
else k=next[k];
}
}
int main()
{
while(scanf("%s",p)!=EOF)
{
plen=strlen(p);
if(plen==1&&p[0]=='.') break;
getNext();
if(plen%(plen-next[plen])==0) printf("%d\n",plen/(plen-next[plen]));
else printf("1\n");
}
return 0;
}
Seek the Name, Seek the Fame
題意:
給定字符串,求出字符串的所有的公共前綴后綴
題解:
其實就是不斷調用next數組,以尋找更短的公共前綴后綴
#include<cstdio>
#include<cstring>
using namespace std;
char p[400010];
int next[400010];
int plen;
void getNext()
{
int j=0;
next[j]=-1;
int k=next[j];
while(j<plen)
{
if(k==-1||p[j]==p[k])
{
next[++j]=++k;
}
else k=next[k];
}
}
void out(int j)
{
if(next[j]==0) return;
out(next[j]);
printf("%d ",next[j]);
}
int main()
{
while(scanf("%s",p)!=EOF)
{
plen=strlen(p);
getNext();
out(plen);
printf("%d\n",plen);
}
return 0;
}
Blue Jeans
題意:
給出n個字符串,找出最長的公共子串,如果有多個,輸出字典順序最小的那個
題解:
暴力拆分第一個字符串的每一個子串(子串長度>=3),然后構造子串的next數組,然后用子串匹配每個2-n這些字符串(匹配時從長度大的子串開始),如果這n-1個字符串都可以成功匹配,那么得出答案
ps:可以通過二分最大長度的方法優化
#include<cstdio>
#include<cstring>
using namespace std;
const int LEN=60;
char str[15][65];
char cpy[65];
char ans[65];
int next[65];
void getNext(char *p,int *next,int len)
{
int j=0;
next[j]=-1;
int k=next[j];
while(j<len)
{
if(k==-1||p[j]==p[k])
{
next[++j]=++k;
}
else k=next[k];
}
}
bool contains(char *str,char *p,int plen)
{
int i=0,j=0;
while(i<LEN)
{
if(j==-1||str[i]==p[j])
{
i++;j++;
}
else j=next[j];
if(j==plen) return true;
}
return false;
}
int main()
{
int t,n;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%s",str[i]);
}
bool found=false;
memset(cpy,0,sizeof(cpy));
ans[0]='Z';
for(int len=60;len>=3;len--)//子串長度
{
if(found) break;
for(int st=0;st+len-1<60;st++)//子串的開始坐標
{
for(int i=0,j=st;i<len;i++,j++)
{
cpy[i]=str[1][j];
}
cpy[len]=0;
getNext(cpy,next,len);
bool allContains=true;
for(int j=2;j<=n;j++)
{
if(!contains(str[j],cpy,len))
{
allContains=false;
break;
}
}
if(allContains)
{
found=true;
if(strcmp(ans,cpy)>0)//字典順序小的優先
{
strcpy(ans,cpy);
ans[len]=0;
}
}
}
}
if(found) printf("%s\n",ans);
else printf("no significant commonalities\n");
}
return 0;
}
Simpsons’ Hidden Talents
題意:
給定兩個字符串s1,s2,求最長的s1前綴ss使得ss為s2的后綴,輸出該字符串和其長度。
題解:
將s1,s2合并后再求整個字符串求next數組,next[len1+len2]就是答案了;
但是next[len]可能會大于len1或大于len2,所以要不斷找更短的公共前綴后綴
#include<cstdio>
#include<cstring>
using namespace std;
char p[100010];
int next[100010];
int plen,len1,len2;
void getNext()
{
int j=0;
next[j]=-1;
int k=next[j];
while(j<plen)
{
if(k==-1||p[j]==p[k])
{
next[++j]=++k;
}
else k=next[k];
}
}
int main()
{
while(scanf("%s",p)!=EOF)
{
len1=strlen(p);
scanf("%s",p+len1);
plen=strlen(p);
len2=plen-len1;
getNext();
if(next[plen]==0||next[plen]==-1)
{
printf("0\n");
}
else
{
while(next[plen]>len1||next[plen]>len2) plen=next[plen];
p[next[plen]]=0;
printf("%s %d\n",p,next[plen]);
}
}
return 0;
}
Count the string
題意:
求出每個前綴在串中的出現次數的總和
題解:
求出next數組,枚舉區間的最長公共前后綴以及更短的的公共前后綴
#include<cstdio>
#include<cstring>
using namespace std;
const int MOD=10007;
char p[200010];
int next[200010];
int plen;
void getNext()
{
int j=0;
next[j]=-1;
int k=next[j];
while(j<plen)
{
if(k==-1||p[j]==p[k])
{
next[++j]=++k;
}
else k=next[k];
}
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%s",&plen,p);
getNext();
int sum=0;
for(int i=1;i<=plen;i++)
{
sum=(sum+1)%MOD;//加上前綴str[0,i-1]本身
int tmp=next[i];
while(tmp)//枚舉區間的最長公共前后綴以及更短的的公共前后綴
{
sum=(sum+1)%MOD;
tmp=next[tmp];
}
}
printf("%d\n",sum);
}
return 0;
}
Substrings
題意:
找到一個子串x,滿足x或x的逆串是輸入的n個字符串的子串,求長度最大的x,輸出x的長度
題解:
其實和上面Blue Jeans那道題一樣的,對第一個字符串暴力拆分,求出它的所有子串,求出next數組,然后對剩下的n-1個字符串正序kmp或者逆序kmp就可以了
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=110;
char str[MAXN][MAXN];
char cpy[MAXN];
int next[MAXN];
int slen[MAXN];
void getNext(char *p,int *next,int plen)
{
int j=0;
next[j]=-1;
int k=next[j];
while(j<plen)
{
if(k==-1||p[j]==p[k])
{
j++;k++;
if(p[j]!=p[k]) next[j]=k;
else next[j]=next[k];
}
else k=next[k];
}
}
bool contains(char *str,char *p,int slen,int plen)
{
int i=0,j=0;
while(i<slen)
{
if(j==-1||str[i]==p[j])
{
i++;j++;
}
else j=next[j];
if(j==plen) return true;
}
return false;
}
bool reverContains(char *str,char *p,int slen,int plen)
{
int i=slen-1,j=0;
while(i>=0)
{
if(j==-1||str[i]==p[j])
{
i--;j++;
}
else j=next[j];
if(j==plen) return true;
}
return false;
}
int main()
{
int t,n;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%s",str[i]);
slen[i]=strlen(str[i]);
}
int length=strlen(str[1]);
bool found=false;
for(int len=length;len>=1;len--)
{
if(found) break;
for(int st=0;st+len-1<length;st++)
{
for(int i=0,j=st;i<len;i++,j++)
{
cpy[i]=str[1][j];
}
cpy[len]=0;
getNext(cpy,next,len);
bool all=true;
for(int i=2;i<=n;i++)
{
if(!contains(str[i],cpy,slen[i],len))
{
if(!reverContains(str[i],cpy,slen[i],len))
{
all=false;
break;
}
}
}
if(all)
{
found=true;
break;
}
};
}
if(found) printf("%d\n",strlen(cpy));
else printf("0\n");
}
return 0;
}
Corporate Identity
題意:
求所有字符的最長公共子串
#include<stdio.h>
#include<string.h>
char str[4040][220];
int next[4040];
void getnext(char *s)
{
int len=strlen(s);
int j=0,k=-1;
next[0]=-1;
while(j<=len)
{
if(k==-1||s[k]==s[j])
{
k++;
j++;
next[j]=k;
}
else
k=next[k];
}
}
int kmp(char *a,char *b)
{
int i,j;
i=j=0;
int n=strlen(a);
int m=strlen(b);
getnext(b);
while(i<n)
{
if(j==-1||a[i]==b[j])
{
i++;
j++;
}
else
j=next[j];
if(j==m)
return 1;
}
return 0;
}
int main()
{
int n;
while(scanf("%d",&n),n)
{
int i,j,k;
for(i=0;i<n;i++)
{
scanf("%s",str[i]);
}
int len=strlen(str[0]);
char temp[220],ans[220]="";
for(i=0;i<len;i++)
{
int cnt=0;
for(j=i;j<len;j++)
{
temp[cnt++]=str[0][j];
temp[cnt]='\0';
int flag=0;
for(k=1;k<n;k++)
{
if(!kmp(str[k],temp))
{
flag=1;
break;
}
}
if(!flag)
{
if(strlen(temp)>strlen(ans)||(strlen(temp)==strlen(ans)&&strcmp(temp,ans)<0))
memcpy(ans,temp,sizeof(temp));
}
}
}
if(strlen(ans)==0)
printf("IDENTITY LOST\n");
else
printf("%s\n",ans);
}
}
String Problem
題意:
給定一個字符串S,在S所有的字符同構字符串中,求出字典序最小的同構字符串的開始位置以及它的出現次數和字典序最大的字符串的開始位置以及它的出現次數.
題解:
顯然,當S剛好啥由循環節構成的字符串時,出現次數是循環節的個數,否則是出現次數是1,而那兩個字典序剛好可以通過最小最大表示法求出來
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=1000010;
int nxt[MAXN],plen;
char p[MAXN];
void getNext()
{
int j=0;
nxt[j]=-1;
int k=nxt[j];
while(j<plen)
{
if(k==-1||p[j]==p[k])
{
nxt[++j]=++k;
}
else k=nxt[k];
}
}
int getMin()
{
int i=0,j=1,k=0;
while(i<plen&&j<plen&&k<plen)
{
int tmp=p[(i+k)%plen]-p[(j+k)%plen];
if(tmp==0) k++;
else
{
if(tmp>0) i=i+k+1;
else j=j+k+1;
if(i==j) j++;
k=0;
}
}
return min(i,j);
}
int getMax()
{
int i=0,j=1,k=0;
while(i<plen&&j<plen&&k<plen)
{
int tmp=p[(i+k)%plen]-p[(j+k)%plen];
if(tmp==0) k++;
else
{
if(tmp>0) j=j+k+1;
else i=i+k+1;
if(i==j) j++;
k=0;
}
}
return min(i,j);
}
int main()
{
while(scanf("%s",p)!=EOF)
{
plen=strlen(p);
getNext();
int len=plen-nxt[plen];
int minIndex=getMin()+1;
int maxIndex=getMax()+1;
if(plen%len==0) printf("%d %d %d %d\n",minIndex,plen/len,maxIndex,plen/len);
else printf("%d %d %d %d\n",minIndex,1,maxIndex,1);
}
}
How many
題意:
給定n個字符串,找出不同的循環同構字符串的個數。
題解:
用最小表示法表示每個字符串,然后再用set或者排序的方法去重即可
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<set>
using namespace std;
const int MAXN=110;
char str[MAXN];
char cpy[MAXN];
int len;
int getMin(char *str)
{
int i=0,j=1,k=0;
while(i<len&&j<len&&k<len)
{
int tmp=str[(i+k)%len]-str[(j+k)%len];
if(tmp==0) k++;
else
{
if(tmp>0) i=i+k+1;
else j=j+k+1;
if(i==j) j++;
k=0;
}
}
return min(i,j);
}
int main()
{
int n,index,i,j;
set<string> ss;
while(scanf("%d",&n)!=EOF)
{
ss.clear();
while(n--)
{
scanf("%s",str);
len=strlen(str);
index=getMin(str);
for(i=index,j=0;i<len;i++,j++)
{
cpy[j]=str[i];
}
for(i=0;j<len;j++,i++)
{
cpy[j]=str[i];
}
cpy[len]=0;
ss.insert(cpy);
}
printf("%d\n",ss.size());
}
}
Problem 1901 Period II
題意:
找出所有的p,滿足:For each prefix with length P of a given string S,if
S[i]=S[i+P] for i in [0..SIZE(S)-p-1],
題解:
其實就是next數組的應用,不斷得到更短的最長公共前綴后綴
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int MAXN=1000010;
int nxt[MAXN],plen;
char p[MAXN];
void getNext()
{
int j=0;
nxt[j]=-1;
int k=nxt[j];
while(j<plen)
{
if(k==-1||p[j]==p[k])
{
nxt[++j]=++k;
}
else k=nxt[k];
}
}
int main()
{
int t,j;
scanf("%d",&t);
queue<int> que;
for(int cas=1;cas<=t;cas++)
{
scanf("%s",p);
plen=strlen(p);
getNext();
j=plen;
while(nxt[j]!=0&&nxt[j]!=-1)
{
que.push(plen-nxt[j]);
j=nxt[j];
}
printf("Case #%d: %d\n",cas,que.size()+1);
while(!que.empty())
{
printf("%d ",que.front());
que.pop();
}
printf("%d\n",plen);
}
}
Theme Section
題意:
給定一個字符串,找出能構成"EAEBE"形式的字符串的E的最長長度。
題解:
因為前面和后面都是E,所以容易想到next數組,然后不斷求更短的公共前綴后綴長度,再判斷中間是不是包含E就可以了,判斷的方法是next[j]是不是等于當前的E的長度
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=1e6+10;
char p[MAXN];
char cpy[MAXN];
int nxt[MAXN];
int plen;
void getFail()
{
int j=0;
nxt[j]=-1;
int k=nxt[j];
while(j<plen)
{
if(k==-1||p[j]==p[k])
{
nxt[++j]=++k;
}
else k=nxt[k];
}
}
int solve()
{
for(int i=nxt[plen];i;i=nxt[i])
{
for(int j=2*i;j<=plen-i+1;j++)//不重疊
{
if(nxt[j]==i)
{
return i;
}
}
}
return 0;
}
int main()
{
int t;
scanf("%d", &t);
while(t--)
{
scanf("%s",p);
plen=strlen(p);
getFail();
printf("%d\n",solve());
}
return 0;
}