动物园

题目链接

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<<"\n";
	}
}

不是很明白为什么会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<<"\n";
	}
}

玄学优化一下,

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<<"\n";
	}
	return 0;
}

O(n)正解做法,

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

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

那么j不从头找了

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

此时的前后缀为

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

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

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

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

posted @ 2020-10-06 18:54  zlq,  阅读(69)  评论(0编辑  收藏  举报