zoukankan      html  css  js  c++  java
  • 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-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
    ",ans);
                while(j>=0&&sub[i]!=sub[j])j=nxt[j];j++;
                while((j<<1)>i+1)j=nxt[j];
                //printf("%d
    ",j);
                ans=ans*(vis[j]+1)%mod;
            }
            printf("%lld
    ",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
    ",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
    ",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
    ",ans);
    }
    

      

  • 相关阅读:
    Hadoop脚本:自动搜集所有节点上的异常信息
    Hadoop脚本:重新初始化所有节点
    Hadoop入门:最应该看的几篇文章
    Hadoop脚本:用Streaming方式使用Hadoop
    [SaaS研究] SaaS在中国 / 中国的Salesforce / 阿里巴巴 / 阿里软件 [ 转 ]
    Hadoop常见问题与解决方法汇总
    Hadoop:你所不知道的一些相关项目
    Hadoop MapReduce 学习
    有多少项目准备和Hadoop比拼?
    开放源码的云计算平台:触手可及的云计算 [ 原 ]
  • 原文地址:https://www.cnblogs.com/Winniechen/p/9144323.html
Copyright © 2011-2022 走看看