KMP

Posted Winniechen

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了KMP相关的知识,希望对你有一定的参考价值。

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);
}

  

以上是关于KMP的主要内容,如果未能解决你的问题,请参考以下文章

kmp算法的个人理解

Python ---- KMP(博文推荐+代码)

KMP算法及Python代码

KMP算法及Python代码

Kmp算法Java代码实现

数据结构—串KMP模式匹配算法