KMP
本质上,kmp就是维护出了一个字符串的前缀的next,并且依据next的某些性质进行字符串匹配。
next:就是最长的前缀和后缀相等的长度
next[i]必定从某一个next[...next[i]]]中得到的,满足s[i]=s[next[i]];
而匹配的时候,满足如果i和j失配,那么必定存在某个s[next[..next[j-1]]+1]=s[i];
例题时间:
[Usaco2015 Feb]Censoring BZOJ3942
分析:
kmp裸题,很显然,我们贪心的将能删除的全部删除就可以了,那么我们如何维护后缀呢?
用栈来维护后缀,考虑将每个主串的对于模式串的匹配位置存一下,之后继续往下匹配就可以了
附上代码:
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <set>
using namespace std;
#define N 1000005
char str[N],sub[N],sta[N];
int pos[N],top,nxt[N],n,m;
void get_next()
{
int j=0;nxt[1]=0;
for(int i=2;i<=n;i++)
{
while(j&&sub[j+1]!=sub[i])j=nxt[j];
nxt[i]=(sub[j+1]==sub[i])?++j:0;
}
}
int main()
{
scanf("%s%s",str+1,sub+1);n=strlen(str+1),m=strlen(sub+1);
get_next();
int j=0;
for(int i=1;i<=n;i++)
{
j=pos[top];sta[++top]=str[i];
while(j&&sub[j+1]!=str[i])j=nxt[j];
if(str[i]==sub[j+1])j++;
if(j==m)top-=m;
else pos[top]=j;
}
for(int i=1;i<=top;i++)printf("%c",sta[i]);puts("");
return 0;
}
BZOJ1355: [Baltic2009]Radio Transmission
结论题:
最短的循环节必定为n-next[n];
证明:如果存在更短的循环节,那么next[i]必定会比当前的更大。并且,s[n-nxt[i]...0]=s[n-nxt[i]-0...nxt[i]];
因此,s必定为以s[1...n-nxt[n]]不停重复+去掉结尾构成的;
附上代码:
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <set>
using namespace std;
#define N 1000005
char str[N],sub[N],sta[N];
int pos[N],top,nxt[N],n,m;
void get_next()
{
int j=0;nxt[1]=0;
for(int i=2;i<=n;i++)
{
while(j&&sub[j+1]!=sub[i])j=nxt[j];
nxt[i]=(sub[j+1]==sub[i])?++j:0;
}
}
int main()
{
scanf("%*d%s",sub+1);n=strlen(sub+1);
get_next();
printf("%d\n",n-nxt[n]);
/*
int j=0;
for(int i=1;i<=n;i++)
{
j=pos[top];sta[++top]=str[i];
while(j&&sub[j+1]!=str[i])j=nxt[j];
if(str[i]==sub[j+1])j++;
if(j==m)top-=m;
else pos[top]=j;
}
for(int i=1;i<=top;i++)printf("%c",sta[i]);puts("");
*/
return 0;
}
BZOJ3670: [Noi2014]动物园
分析:
我们考虑其实答案就是while(i){if(i<=x/2)num++;i=next[i]}
所以,我们将i的所有next[i]有多少个存一下,之后如果存在i<=x/2那么,所有i的子串都满足<=x/2
因此,我们每次只要找到第一个i<=x/2的就可以了,如果暴力去找会TLE
那么,我们考虑如果i>x/2那么i+1>(x+1)/2;因此,每次找的时候,直接找上一次找到的下一个就可以了。
附上代码:
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <set>
using namespace std;
#define N 1000005
#define mod 1000000007
char str[N],sub[N],sta[N];
int pos[N],top,nxt[N],n,m;int vis[N];
void get_next()
{
int i=0,j=-1;nxt[0]=-1;vis[0]=0;
while(i<m)
{
if(j==-1||sub[j]==sub[i])
{
nxt[++i]=++j;
vis[i]=vis[j]+1;
}else j=nxt[j];
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
memset(vis,0,sizeof(vis));
scanf("%s",sub);m=strlen(sub);
get_next();
long long ans=1;int j=0;
for(int i=1;i<m;i++)
{
//printf("%lld\n",ans);
while(j>=0&&sub[i]!=sub[j])j=nxt[j];j++;
while((j<<1)>i+1)j=nxt[j];
//printf("%d\n",j);
ans=ans*(vis[j]+1)%mod;
}
printf("%lld\n",ans);
}
/*
int j=0;
for(int i=1;i<=n;i++)
{
j=pos[top];sta[++top]=str[i];
while(j&&sub[j+1]!=str[i])j=nxt[j];
if(str[i]==sub[j+1])j++;
if(j==m)top-=m;
else pos[top]=j;
}
for(int i=1;i<=top;i++)printf("%c",sta[i]);puts("");
*/
return 0;
}
BZOJ1511:[POI2006]OKR-Periods of Words
分析:
看起来很像前面的某一道题,只是变成了最长循环节...那么就是找到一个最小的i满足前缀后缀相同就可以了
附上代码:
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <set>
using namespace std;
#define N 1000005
char s[N];int n,nxt[N];
long long ans;
void get_next()
{
nxt[1]=0;int j=0;
for(int i=2;i<=n;i++)
{
while(j&&s[i]!=s[j+1])j=nxt[j];
nxt[i]=(s[i]==s[j+1])?++j:0;
}
}
int main()
{
scanf("%d%s",&n,s+1);get_next();
for(int i=1;i<=n;i++)
{
while(nxt[nxt[i]])nxt[i]=nxt[nxt[i]];
if(nxt[i])ans+=i-nxt[i];
}
printf("%lld\n",ans);
return 0;
}
BZOJ3620: 似乎在梦中见过的样子
分析:
n=15000的n^2题实在是太毒了...
几乎和动物园是一模一样的,只是多了一个K而已,那么就是每次更新num的时候判断一下是否大于K就好了,之后枚举起点,剩下的和动物园一样了
附上代码:
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <set>
using namespace std;
#define N 1000005
#define mod 1000000007
char str[N],sub[N],sta[N];
int pos[N],top,nxt[N],n,m,k;
void get_next()
{
int i=0,j=-1;nxt[0]=-1;
while(i<m)
{
if(j==-1||sub[j]==sub[i])
{
nxt[++i]=++j;
}else j=nxt[j];
}
}
int main()
{
scanf("%s%d",sub,&k);m=strlen(sub);
long long ans=0;
while(m)
{
get_next();int j=0;
for(int i=1;i<m;i++)
{
while(j>=0&&sub[i]!=sub[j])j=nxt[j];j++;
while((j<<1)>=i+1)j=nxt[j];
if(j>=k)ans++;
}
for(int i=1;i<=m;i++)sub[i-1]=sub[i];
m=strlen(sub);
}
printf("%lld\n",ans);
/*
int j=0;
for(int i=1;i<=n;i++)
{
j=pos[top];sta[++top]=str[i];
while(j&&sub[j+1]!=str[i])j=nxt[j];
if(str[i]==sub[j+1])j++;
if(j==m)top-=m;
else pos[top]=j;
}
for(int i=1;i<=top;i++)printf("%c",sta[i]);puts("");
*/
return 0;
}
BZOJ1009: [HNOI2008]GT考试
分析:
说实话,之前并不敢写这道题,写完发现这题水的一批...
如果长度没有那么长的话,之间f[i][j]表示第i个字符匹配到了不吉利数字的第j个...之后用next转移就可以了
那么长度长一点那么就矩阵乘法呗...其实可以不用KMP求next的,但是我觉得暴力求比KMP求还麻烦
附上代码:
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <iostream>
#include <set>
using namespace std;
#define N 25
int n,m,mod,nxt[N];char s[N];
struct node
{
int a[N][N];
friend node operator*(const node &d,const node &b)
{
node c;memset(c.a,0,sizeof(c.a));
for(int i=0;i<m;i++)
{
for(int j=0;j<m;j++)
{
for(int k=0;k<m;k++)
{
c.a[i][j]=(c.a[i][j]+d.a[i][k]*b.a[k][j])%mod;
}
}
}
return c;
}
}ret,map;
void get_next()
{
int j=0;nxt[1]=0;
for(int i=2;i<=m;i++)
{
while(j&&s[j+1]!=s[i])j=nxt[j];
nxt[i]=(s[j+1]==s[i])?++j:0;
}
}
void q_pow(int n)
{
for(int i=0;i<m;i++)ret.a[i][i]=1;
while(n)
{
if(n&1)ret=ret*map;
map=map*map;n=n>>1;
}
return ;
}
void print(const node &d)
{
for(int i=0;i<m;i++)
{
for(int j=0;j<m;j++)
{
printf("%d ",d.a[i][j]);
}
puts("");
}
}
int vis[N],sum;
int main()
{
scanf("%d%d%d",&n,&m,&mod);
scanf("%s",s+1);get_next();
for(int i=0;i<m;i++)
{
memset(vis,0,sizeof(vis));
int j=i,num=0;
while(1)
{
if(!vis[s[j+1]-'0'])
{
vis[s[j+1]-'0']=1,num++;
if(j+1<m)map.a[i][j+1]=1;
}
if(!j)break;
j=nxt[j];
}
map.a[i][0]=10-num;
}
//print(map);
q_pow(n);
int ans=0;
for(int i=0;i<m;i++)
{
ans=(ans+ret.a[0][i])%mod;
}
printf("%d\n",ans);
}