BZOJ4650: [Noi2016]优秀的拆分

考场上没秒的话多拿5分并不划算的样子。

思想其实很简单嘛。

要统计答案,求以每个位置开始和结束的AA串数量就好了。那么枚举AA中A的长度L,每L个字符设一个关键点,这样AA一定经过相邻的两个关键点。计算出相邻关键点的最长公共前后缀,把对应的位置区间加一下。

求lcp和lcs可以用后缀数组,也可以用hash。

UPD:UOJ上有人sxbk把原来的hash卡了,稍微改了下。其实base随机就没事了,然而BZOJ上并不能调用time(0)。

#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
const int N=30005;
const int p=1e9+93;
ll f[N][2],g[N][2];
bool jud(int i1,int j1,int i2,int j2){
	return f[j1][1]-f[i1][1]*g[j1-i1][1]==f[j2][1]-f[i2][1]*g[j2-i2][1]&&0==(f[j1][0]+(p-f[i1][0])*g[j1-i1][0]+p-f[j2][0]+f[i2][0]*g[j2-i2][0])%p;
}
char z[N];
int a[N],b[N];
int main(){
	g[0][0]=1;
	g[0][1]=1;
	for(int i=1;i<N;++i){
		g[i][0]=2003*g[i-1][0]%p;
		g[i][1]=2011*g[i-1][1];
	}
	int q;
	scanf("%d",&q);
	while(q--){
		scanf("%s",z+1);
		int n=strlen(z+1);
		for(int i=1;i<=n;++i){
			f[i][0]=(z[i]+2003*f[i-1][0])%p;
			f[i][1]=z[i]+2011*f[i-1][1];
		}
		fill_n(a+1,n,0);
		fill_n(b+1,n,0);
		for(int i=1;i<=n/2;++i)
			for(int j=1;j<=n-i;j+=i)
				if(z[j]==z[j+i]){
					int l=0,r=min(i,j);
					while(l!=r){
						int m=l+r+1>>1;
						if(jud(j-m,j,j+i-m,j+i))
							l=m;
						else
							r=m-1;
					}
					int u=j-l+1;
					l=0,r=min(i-1,n-j-i);
					while(l!=r){
						int m=l+r+1>>1;
						if(jud(j,j+m,j+i,j+i+m))
							l=m;
						else
							r=m-1;
					}
					int v=j+l+1;
					if(u+i<=v){
						++a[u];
						--a[v-i+1];
						++b[u+i*2-1];
						--b[v+i];
					}
				}
		ll s=0;
		for(int i=1;i<=n;++i){
			a[i]+=a[i-1];
			b[i]+=b[i-1];
			s+=a[i]*b[i-1];
		}
		printf("%lld\n",s);
	}
}
posted @ 2016-08-29 00:50  f321dd  阅读(713)  评论(0编辑  收藏  举报