KMP+dp NOI2014 动物园(PS:不是官方做法)

概括:给定一个长度为L的字符串,定义sum[i]为其前i个字符组成的不重叠的公共前后缀的数量;求:(sum1+1)*(sum2+1)*...*(sumL+1)%1 000 000 007; 暴力就不说了,手工模拟;思路2:先求出给定字符串的fail数组,KMP往前跳,直到f[j]<=i/2开始计数,最后再跳几次跳到0就是该段的sum; //这一段请好好理解KMP的原理 思路3:对于思路2的优化是时候记忆化dp了! g[i]表示长度为i的前缀串最多还要几次长度才能缩短成0; g[i] = g[f[i]]+1然后对于f[i]=j,记录l[j]=i;h[i]表示i能够跳到的第一个编号小于等于i/2的位置; 计算h[i]倒序计算,很容易理解在h[l[i]]的基础上再跳最多几步就可以达到h[i]; 由于memset及其耗时间,所以索性开了5个l; 

基本是线性算法了 

额。。。如果看了有什么不明白的可以问我(PS:这个不是标准解,想要看标准解可以去其他大牛的博客)

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn=1000005;
const int mo=1000000007;
typedef long long LL;

int N;
char T[maxn];
LL ans;
int f[maxn],g[maxn],h[maxn],l[6][maxn];

void getFail(char *p,int *f,int t)
{
	f[0]=f[1]=0;
	int n = strlen(p);
	for(int i = 1; i < n; i++)
	{
		int j = f[i];
		while(j && p[i] != p[j]) j = f[j];
		if(p[i]==p[j])
		{
			f[i+1] = j+1;
			l[t][j+1] = i+1;
		}
		else f[i+1] = 0;
	}
}
int calc2(int i)
{
	int j;
	j = f[i];
	while(j > i/2) j = f[j];
	return j;
}
void dp(int n,int t)
{
	g[0] = 0;
	h[0] = 0;
	for(int i = 1; i <= n; i++)
	{
		g[i] = g[f[i]]+1;
	}
	h[n] = calc2(n);
	for(int i = n-1; i >= 1; i--)
	{
		if(l[t][i] != 0)
		{
			int j = h[l[t][i]];
			while(j > i/2) j = f[j];
			h[i] = j;
		}
		else h[i] = calc2(i); 
	}
}
void work3(int t)
{
	getFail(T,f,t);
	int n = strlen(T);
	ans = 1;
	dp(n,t);
	int j;
	for(int i = 2; i <= n; i++)
	{
		j = h[i];
		ans = ans*(g[j] + 1)%mo;
	}
	if(t != N) printf("%d\n",ans);
	else printf("%d",ans);
}
int main()
{
	freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);
	scanf("%d\n",&N);
	for(int i = 1; i <= N; i++)
	{
		gets(T);
//		work1(i);
//		work2(i);
		work3(i);
	}
	return 0;
}



你可能感兴趣的:(KMP)