• 动物园


    题目链接

    ps下文的next和fail是一个东西

    注意题目描述中对next的讲解

    “对于字符串S的前i个字符构成的子串,既是它的后缀又是它的前缀的字符串中(它本身除外),最长的长度记作next[i]。”

    num数组:对于字符串S的前i个字符构成的子串,既是它的后缀同时又是它的前缀的字符串的数量

    一个性质:当前串的前缀(后缀)的前缀(后缀)也是当前串的前缀(后缀)

    将前缀(后缀)的next的数组作为指父亲的边会建成一棵树

    num其实就是树上结点的深度?

    还有点差别……题目中的前缀和后缀是不能重叠的

    先把深度记为ans

    考虑当前串的一个不大于它长度的1/2的前缀(后缀)子串,这个子串的ans其实是i的num

    默上板子之后暴跳倍增找一下上文所述的子串

    #include<bits/stdc++.h>
    using namespace std;
    char s[2000000];
    int fail[2000000][37],ans[2000000];
    int mod=1e9+7;
    int main()
    {
    	int n;
    	cin>>n;
    	while(n--)
    	{
    		memset(ans,0,sizeof(ans));
    		memset(fail,0,sizeof(fail));
    		cin>>s+1;
    		int ls=strlen(s+1);
    		int j=0;
    		ans[1]=1;
    		for(int i=2;i<=ls;i++)//i如果从1开始会使i和j始终指在同一点上,匹配的时候要错开一位 
    		{
    			while(j&&s[j+1]!=s[i]) j=fail[j][0];
    			if(s[j+1]==s[i]) j++;
    			fail[i][0]=j;
    			ans[i]=ans[j]+1;
    		}
    		for(int i=1;i<=ls;i++)
    		for(int k=1;k<=30;k++) 
    			fail[i][k]=fail[fail[i][k-1]][k-1];
    		long long answer=1;
    		for(int i=1;i<=ls;i++)
    		{
    			int j=i;
    			for(int k=30;k>=0;k--)
    			if(2*fail[j][k]>i&&fail[j][k]) j=fail[j][k];
    			j=fail[j][0];
    			answer*=(long long)(ans[j]+1);
    			answer%=mod;
    		}
    		cout<<answer<<"
    ";
    	}
    }
    

    不是很明白为什么会t,感觉复杂度挺对的其实,,,

    #include<bits/stdc++.h>
    using namespace std;
    char s[2000000];
    int fail[37][2000000],ans[2000000];
    int mod=1e9+7;
    int main()
    {
    	register int n;
    	cin>>n;
    	while(n--)
    	{
    		scanf("%s",s+1);
    		register int ls=strlen(s+1);
    		register int j=0;
    		ans[1]=1;
    		for(register int i=2;i<=ls;i++)//i如果从1开始会使i和j始终指在同一点上,匹配的时候要错开一位 
    		{
    			while(j&&s[j+1]!=s[i]) j=fail[0][j];
    			if(s[j+1]==s[i]) j++;
    			fail[0][i]=j;
    			ans[i]=ans[j]+1;
    		}
    		for(register int k=1;k<=20;k++) 
    		for(register int i=1;i<=ls;i++)
    			fail[k][i]=fail[k-1][fail[k-1][i]];//不能放到上面的循环里面求,fail1求不出来 
    		long long answer=1;
    		for(register int i=1;i<=ls;i++)
    		{
    			register int j=i;
    			for(register int k=20;k>=0;k--)
    			if(2*fail[k][j]>i&&fail[k][j]) j=fail[k][j];
    			j=fail[0][j];
    			answer*=(long long)(ans[j]+1);
    			answer%=mod;
    		}
    		cout<<answer<<"
    ";
    	}
    }
    

    玄学优化一下,

    1.按照题解里把倍增二维反过来莫名其妙快了好多,,不懂为什么

    2.读入字符串要比读入字符快(?)

    #include<bits/stdc++.h>
    using namespace std;
    char s[2000000];
    int fail[2000000],ans[2000000];
    int mod=1e9+7;
    int main()
    {
    	register int n;
    	cin>>n;
    	while(n--)
    	{
    		scanf("%s",s+1);
    		register int ls=strlen(s+1);
    		register int j=0;
    		ans[1]=1;
    		for(register int i=2;i<=ls;i++)//i如果从1开始会使i和j始终指在同一点上,匹配的时候要错开一位 
    		{
    			while(j&&s[j+1]!=s[i]) j=fail[j];
    			if(s[j+1]==s[i]) j++;
    			fail[i]=j;
    			ans[i]=ans[j]+1;
    		}
    		long long answer=1;
    		j=0;
    		for(register int i=2;i<=ls;i++)
    		{
    			while(s[j+1]!=s[i]&&j) j=fail[j];
    			if(s[j+1]==s[i]) j++;
    			if(j<<1>i&&j) j=fail[j];
    			answer*=(long long)(ans[j]+1);
    			answer%=mod;
    		}
    		cout<<answer<<"
    ";
    	}
    	return 0;
    }
    

    O(n)正解做法,

    这个过程本质上是再找一个前缀(后缀),同时要求长度不超过1/2

    这个过程其实和kmp板子的匹配过程还是很相似的?

    那么j不从头找了

    考虑每一次i+1后字符串多了一位字符

    此时的前后缀为

    1.上一次匹配的j+1(新加的一位和前面匹配成功)

    2.跳fail直到匹配成功或调到头了(新加的一位和前面匹配失败)

    因为原来的j小于原来的i/2,那么j肯定也小于(i+1)/2

    最多匹配成功多加了一位,特判一下j需不需要-1就可以啦~

  • 相关阅读:
    spring前两天
    代理模式
    学习spring第三天
    Spring集成MyBatis配置文件
    学习spring第二天
    学习spring第一天
    Bind关闭递归查询
    织梦DedeCMS v5.7 实现导航条下拉菜单
    Debian下修改系统时间的方式
    js禁止页面复制功能禁用页面右键菜单示例代码
  • 原文地址:https://www.cnblogs.com/qwq-/p/13774220.html
Copyright © 2020-2023  润新知