http://www.matrix67.com/blog/archives/115
拓展KMP:还是刘雅琼的PPT讲得最清晰(ORZ)
http://wenku.baidu.com/view/8e9ebefb0242a8956bece4b3.html
接下来就是题集了
【HDU 1358】 period:这道题考察了KMP的基本应用
如果前面部分是一个循环,那么循环节的必然为i-next[i]
如果i%(i-next[i])==0则可以得到依次对应相等。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int SIZEN=1000005;
char str[SIZEN];
int next[SIZEN];
void getnext(char *s,int len){
next[0]=next[1]=0;
for(int i=1;i<len;i++){
int j=next[i];
while(j&&s[j]!=s[i]) j=next[j];
next[i+1]=s[j]==s[i]?j+1:0;
}
}
int main()
{
int i,j;
int n,m;
int len,txt=1;
while(scanf("%d",&len)!=EOF&&len){
scanf("%s",str);
getnext(str,len);
printf("Test case #%d\n",txt++);
for(i=1;i<=len;i++){
if(next[i]&&i%(i-next[i])==0)
printf("%d %d\n",i,i/(i-next[i]));
}
printf("\n");
}
return 0;
}
【HDU 1711】Number Sequence:
这道题是KMP的最基础的应用了。只是注意数有正负之分(一开始没注意想用输入外挂提速跪得意识模糊)
#include<cstdio>
#include<cstring>
const int SIZE=1000005;
int str1[SIZE],str2[10005];
int next[10005];
int len1,len2;
void getnext()
{
next[0]=next[1]=0;
for(int i=1;i<len2;i++)
{
int j=next[i];
while(j&&str2[j]!=str2[i]) j=next[j];
next[i+1]=str2[j]==str2[i]?j+1:0;
}
}
int main()
{
//freopen("data.in","r",stdin);
int i,j,t,k;
scanf("%d",&t);
while(t--)
{
scanf("%d %d",&len1,&len2);
for(i=0;i<len1;i++) scanf("%d",&str1[i]);
for(i=0;i<len2;i++) scanf("%d",&str2[i]);
getnext();
j=k=0;
for(i=0;i<len1;i++)
{
while(j&&str2[j]!=str1[i]) j=next[j];
if(str2[j]==str1[i]) j++;
if(j==len2) {k=i-j+2;break;}
}
if(k) printf("%d\n",k);
else printf("-1\n");
}
return 0;
}
【HDU 2203】亲和串:
这道题仅仅需要把主串复制一遍,然后用KMP扫一下就OK了
不过要注意模版串的长度大于主串长度的情况
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int SIZEN=300005;
char s1[SIZEN],s2[SIZEN];
int f[SIZEN];
void getfail(char *s,int len){
f[0]=f[1]=0;
for(int i=1;i<len;i++){
int j=f[i];
while(j&&s[j]!=s[i]) j=f[j];
f[i+1]=s[j]==s[i]?j+1:0;
}
}
int main()
{
int i,j;
int n,m;
while(scanf("%s%s",s1,s2)!=EOF){
int len1=strlen(s1),len2=strlen(s2);
memcpy(s1+len1,s1,sizeof(char)*(len1+1));
if(len1<len2){
printf("no\n");
continue;
}
len1*=2;
getfail(s2,len2);
int ans=0;
int j=0;
for(i=0;i<len1;i++){
while(j&&s2[j]!=s1[i]) j=f[j];
if(s2[j]==s1[i]) j++;
if(j==len2) {ans=1;break;}
}
if(ans) printf("yes\n");
else printf("no\n");
}
return 0;
}
【HDU 3336】 Count the string:
这道题处于一种数据奇弱的状态,而本弱之前用错误代码AC了之后再也没关注过他
后来在CF上做到一道类似题顿时傻眼,后来才听说这道题是一模一样的。
故重新研究了一下。
我们要求与前缀相同的子串的总和
就要求不同长度与前缀相同的子串的和
故设cnt[i]为与长度为i的前缀相同的子串的个数
KMP中的f数组的意思是f[i-next[i]...i-1]==f[0..next[i]-1]
因此可以得到cnt[next[i]]=cnt[next[i]]+cnt[i];
若i从大到小递推就可以得到字串的个数,因为若从小到大的话
在较长子串中会包含有较小字串,而由较小字串推更小子串的时候,较小子串是没有统计完全的
因此需要从大到小。而KMP中的f数组记录的是:与前缀相同的最长数列。故正确性得到了。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define MOD 10007
using namespace std;
const int SIZEN=200005;
char str[SIZEN];
int f[SIZEN];
int cnt[SIZEN];
void getfail(char s[],int len){
f[0]=f[1]=0;
for(int i=1;i<len;i++){
int j=f[i];
while(j&&s[j]!=s[i]) j=f[j];
f[i+1]=s[j]==s[i]?j+1:0;
}
}
int main()
{
int i,j;
int n,m,_;
int len;
scanf("%d",&_);
while(_--){
scanf("%d",&len);
scanf("%s",str);
getfail(str,len);
memset(cnt,0,sizeof(cnt));
for(i=len;i>0;i--) cnt[i]++;//前缀自身的串数
for(i=len;i>0;i--) cnt[f[i]]=(cnt[f[i]]+cnt[i])%MOD;//统计每个长度子串的个数
int ans=0;
for(i=1;i<=len;i++) ans=(ans+cnt[i])%MOD;
printf("%d\n",ans);
}
return 0;
}
codeforces #246 div2 PD与此题异曲同工,可以尝试一做
【HDU 3613】Best Reward
分析题意:通过讲字符串一分为二,得到一个以上的权值最大的回文子串
因为是一分为二,故回文子串只可能在头或尾产生,因此就想到了扩展KMP
EKMP中extend[i]的定义是:主串中以s[i]开头的子串与模式串前缀的最长长度
因此就想到:能不能将字符串反转,然后做两次EKMP,就可以判断了?
答案是肯定的。那再用前缀和记录前后的val,若为回文串就加上,扫一遍取最大值accepted get!
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int SIZEN=500005;
char str[SIZEN],sstr[SIZEN];
int next[SIZEN];
int extend1[SIZEN],extend2[SIZEN];
int val[SIZEN],len;
void EKMP(char s[],char t[],int extend[]){
int i,j,p,l;
next[0]=len;
j=0;
while(1+j<len&&t[j]==t[1+j]) j++;
next[1]=j;
int a=1;
for(i=2;i<len;i++){
p=next[a]+a-1;
l=next[i-a];
if(i+l<p+1) next[i]=l;
else{
j=max(0,p-i+1);
while(i+j<len&&t[i+j]==t[0+j]) j++;
next[i]=j;
a=i;
}
}
j=0;
while(j<len&&j<len&&s[j]==t[j]) j++;
extend[0]=j;
a=0;
for(i=1;i<len;i++){
p=extend[a]+a-1;
l=next[i-a];
if(l+i<p+1) extend[i]=l;
else{
j=max(0,p-i+1);
while(i+j<len&&j<len&&s[i+j]==t[j]) j++;
extend[i]=j;
a=i;
}
}
}
int main()
{
//freopen("data.in","r",stdin);
int i,j;
int n,m,_;
int v[100];
scanf("%d",&_);
while(_--){
for(i=0;i<26;i++) scanf("%d",&v[i]);
scanf("%s",str);
len=strlen(str);
for(i=0;i<len;i++)
sstr[len-1-i]=str[i];
sstr[len]='\0';
EKMP(str,sstr,extend1);
EKMP(sstr,str,extend2);//1是以原串为主串,2是以逆串为主串
val[0]=v[str[0]-'a'];
for(i=1;i<len;i++) val[i]=val[i-1]+v[str[i]-'a'];
int ans=0,tans;
for(i=1;i<len;i++)
{
tans=0;
if(extend1[i]==len-i) tans+=val[len-1]-val[i-1];
if(extend2[len-i]==i) tans+=val[i-1];
ans=max(ans,tans);
}
printf("%d\n",ans);
}
return 0;
}
【HDU 3746】Cyclic Nacklace
题意是问你,最少加上多少个珠子可以使整个串有个循环
首先,既然是循环,那么循环节至少有两个
其次,再想想KMP中f数组的定义
就可以知道:若存在循环,则循环节长度必然是len-next[len]==round
若round==len,那么只有一个循环节,就应当补充len个
若l en%round==0 则存在两个以上循环节(1个已经被排除了),输出0
若都不满足,则不满足循环的串的长度为len%(round),需要补充的长度为round-len%(round)
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int SIZEN=100005;
char str[SIZEN];
int f[SIZEN];
void getfail(char *s,int len){
f[0]=f[1]=0;
for(int i=1;i<len;i++){
int j=f[i];
while(j&&s[i]!=s[j]) j=f[j];
f[i+1]=s[i]==s[j]?j+1:0;
}
}
int main()
{
int i,j;
int n,m,_;
scanf("%d",&_);
while(_--){
scanf("%s",str);
int len=strlen(str);
int ans=len;
getfail(str,strlen(str));
int round=len-f[len];
if(len==round) printf("%d\n",round);
else if(len%round==0) printf("%d\n",0);
else{
ans=(round-len%round);
printf("%d\n",ans);
}
}
return 0;
}
【HDU 4300】Clairewd’s message
扩展KMP的水题,输入两行,第一行是密码表
第二行是暗文和明文混在一起的文字,问:最短暗文和明文都存在的字符串是什么
因此必然想到,将此串的文字全部翻译,然后计算原串后缀与模版串的最长前缀的相同长度
还有一点需要注意的是,暗文肯定不会比密文短,因此记得判断一下
#include<cstdio>
#include<cstring>
#include<algorithm>
#define REP(a,b) for(i=a;i<b;i++)
using namespace std;
const int SIZEN=100005;
char s1[SIZEN],s2[SIZEN];
char table[SIZEN];
char change[SIZEN];
int next[SIZEN],extend[SIZEN];
void EKMP(char s[],char t[]){
int i,j,p,l;
int len=strlen(t);
int len1=strlen(s);
memset(next,0,sizeof(next));
memset(extend,0,sizeof(extend));
next[0]=len;
j=0;
while(1+j<len&&t[j]==t[1+j]) j++;
next[1]=j;
int a=1;
for(i=2;i<len;i++){
p=next[a]+a-1;
l=next[i-a];
if(i+l<p+1) next[i]=l;
else{
j=max(0,p-i+1);
while(i+j<len&&t[i+j]==t[0+j]) j++;
next[i]=j;
a=i;
}
}
j=0;
while(j<len1&&j<len&&s[j]==t[j]) j++;
extend[0]=j;
a=0;
for(i=1;i<len1;i++){
p=extend[a]+a-1;
l=next[i-a];
if(l+i<p+1) extend[i]=l;
else{
j=max(0,p-i+1);
while(i+j<len1&&j<len&&s[i+j]==t[j]) j++;
extend[i]=j;
a=i;
}
}
}
int main()
{
int i,j;
int n,m,_;
scanf("%d",&_);
while(_--){
scanf("%s",table);
REP(0,26) change[table[i]-'a'+1]=i+'a';
scanf("%s",s1);
int len=strlen(s1);
REP(0,len) s2[i]=change[s1[i]-'a'+1];
EKMP(s1,s2);
int ans;
REP(0,len+1)
if(extend[i]+i==len&&i>=extend[i]){
ans=i;
break;
}
REP(0,ans) printf("%c",s1[i]);
REP(0,ans) printf("%c",change[s1[i]-'a'+1]);
printf("\n");
}
return 0;
}