【BZOJ4650】【NOI2016】优秀的拆分(后缀数组)

【BZOJ4650】【NOI2016】优秀的拆分(后缀数组)

题面

BZOJ
Uoj

题解

如果我们知道以某个位置为开始/结尾的\(AA\)串的个数
那就直接做一下乘法就好
这个怎么求?
枚举一个位置
枚举串的长度
直接暴力算就好啦
至于是否可行,用\(SA\)\(lcp\)就好啦
这样就是\(95\)
NOI这么好拿部分分的???

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define MAX 35000
int x[MAX],y[MAX],t[MAX];
int SA[MAX],height[MAX],rk[MAX];
int lg[MAX],n,p[20][MAX],a[MAX];
char s[MAX];
int g[MAX],f[MAX],T;
bool cmp(int i,int j,int k){return y[i]==y[j]&&y[i+k]==y[j+k];}
void init()
{
	memset(SA,0,sizeof(SA));
	memset(height,0,sizeof(height));
	memset(rk,0,sizeof(rk));
	memset(x,0,sizeof(x));
	memset(y,0,sizeof(y));
	memset(t,0,sizeof(t));
	memset(a,0,sizeof(a));
}
void GetSA()
{
	int m=50;
	for(int i=1;i<=n;++i)t[x[i]=a[i]]++;
	for(int i=1;i<=m;++i)t[i]+=t[i-1];
	for(int i=n;i>=1;--i)SA[t[x[i]]--]=i;
	for(int k=1;k<=n;k<<=1)
	{
		int p=0;
		for(int i=n-k+1;i<=n;++i)y[++p]=i;
		for(int i=1;i<=n;++i)if(SA[i]>k)y[++p]=SA[i]-k;
		for(int i=0;i<=m;++i)t[i]=0;
		for(int i=1;i<=n;++i)t[x[y[i]]]++;
		for(int i=1;i<=m;++i)t[i]+=t[i-1];
		for(int i=n;i>=1;--i)SA[t[x[y[i]]]--]=y[i];
		swap(x,y);
		x[SA[1]]=p=1;
		for(int i=2;i<=n;++i)
			x[SA[i]]=cmp(SA[i],SA[i-1],k)?p:++p;
		if(p>=n)break;
		m=p;
	}
	for(int i=1;i<=n;++i)rk[SA[i]]=i;
	for(int i=1,j=0;i<=n;++i)
	{
		if(j)--j;
		while(a[i+j]==a[SA[rk[i]-1]+j])++j;
		height[rk[i]]=j;
	}
}
void Pre()
{
	memset(p,63,sizeof(p));
	for(int i=1;i<=n;++i)p[0][i]=height[i];
	for(int j=1;j<15;++j)
		for(int i=1;i<=n;++i)
			p[j][i]=min(p[j-1][i],p[j-1][i+(1<<(j-1))]);
}
int Query(int i,int j)
{
	return min(p[lg[j-i+1]][i],p[lg[j-i+1]][j-(1<<lg[j-i+1])+1]);
}
int lcp(int i,int j)
{
	int l=min(rk[i],rk[j])+1,r=max(rk[i],rk[j]);
	return Query(l,r);
}
int main()
{
	for(int i=2;i<=30000;++i)lg[i]=lg[i>>1]+1;
	scanf("%d",&T);
	while(T--)
	{
		init();
		scanf("%s",s+1);
		n=strlen(s+1);
		for(int i=1;i<=n;++i)a[i]=s[i]-96;
		GetSA();Pre();
		for(int i=1;i<=n;++i)
		{
			g[i]=0;
			for(int l=1;l+l+i-1<=n;++l)
				if(lcp(i,i+l)>=l)g[i]++;
		}
		for(int i=2;i<=n;++i)
		{
			f[i]=0;
			for(int l=1;i-l-l+1>0;++l)
				if(lcp(i-l-l+1,i-l+1)>=l)f[i]++;
		}
		int ans=0;
		for(int i=1;i<n;++i)
			ans+=f[i]*g[i+1];
		printf("%d\n",ans);
	}
	return 0;
}

\(95\)分的暴力太显然了。。
原来\(NOI\)都是这样送分???
为什么NOIP 没有这么好的福利


想想怎么优化吧。。。
肯定不能枚举长度之后再暴力算每一个位置
那么,我们要考虑一个方法,
可以一次性算出连续的位置

想想我们怎么求\(AA\)这种形式??
计算\(lcp(i,i+len)>=len\)是否成立
但是,如果\(lcp(i,i+len)>=len\)
我们就会发现,有一段区间内都是有满足条件的子串
所以我们可以一起计算

现在仔细思考怎么算
因为每次是\(i\)\(i+len\)
所以我们只要枚举位置是\(len\)的倍数的地方就好
旁边的地方我们要想办法算出来
第一个,是向后如果可以增加的话
\(lcp(i,i+len)>=L\)我就会获得向后的一段连续区间
如果只算向后,会忽略掉向前的一段
所以再算一下\(lcs(i,i+len)\)这段,这两边拼起来
如果满足条件,证明这一段区间都是可行的
这样就可以差分全部\(+1\)
如果重复的部分够多
这样算可能会影响到别的块里面
所以要强制只在自己这一段里面算
具体的实现看代码啦

 #include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
using namespace std;
#define ll long long
#define MAX 35000
int lg[MAX],n;
char s[MAX];
int g[MAX],f[MAX],T;
struct SA
{
	int p[20][MAX],a[MAX];
	int x[MAX],y[MAX],t[MAX];
	int SA[MAX],height[MAX],rk[MAX];
	bool cmp(int i,int j,int k){return y[i]==y[j]&&y[i+k]==y[j+k];}
	void init()
		{
			memset(SA,0,sizeof(SA));
			memset(height,0,sizeof(height));
			memset(rk,0,sizeof(rk));
			memset(x,0,sizeof(x));
			memset(y,0,sizeof(y));
			memset(t,0,sizeof(t));
			memset(a,0,sizeof(a));
		}
	void GetSA()
		{
			int m=50;
			for(int i=1;i<=n;++i)t[x[i]=a[i]]++;
			for(int i=1;i<=m;++i)t[i]+=t[i-1];
			for(int i=n;i>=1;--i)SA[t[x[i]]--]=i;
			for(int k=1;k<=n;k<<=1)
			{
				int p=0;
				for(int i=n-k+1;i<=n;++i)y[++p]=i;
				for(int i=1;i<=n;++i)if(SA[i]>k)y[++p]=SA[i]-k;
				for(int i=0;i<=m;++i)t[i]=0;
				for(int i=1;i<=n;++i)t[x[y[i]]]++;
				for(int i=1;i<=m;++i)t[i]+=t[i-1];
				for(int i=n;i>=1;--i)SA[t[x[y[i]]]--]=y[i];
				swap(x,y);
				x[SA[1]]=p=1;
				for(int i=2;i<=n;++i)
					x[SA[i]]=cmp(SA[i],SA[i-1],k)?p:++p;
				if(p>=n)break;
				m=p;
			}
			for(int i=1;i<=n;++i)rk[SA[i]]=i;
			for(int i=1,j=0;i<=n;++i)
			{
				if(j)--j;
				while(a[i+j]==a[SA[rk[i]-1]+j])++j;
				height[rk[i]]=j;
			}
		}
	void Pre()
		{
			memset(p,63,sizeof(p));
			for(int i=1;i<=n;++i)p[0][i]=height[i];
			for(int j=1;j<15;++j)
				for(int i=1;i<=n;++i)
					p[j][i]=min(p[j-1][i],p[j-1][i+(1<<(j-1))]);
		}
	int Query(int i,int j)
		{
			return min(p[lg[j-i+1]][i],p[lg[j-i+1]][j-(1<<lg[j-i+1])+1]);
		}
	int lcp(int i,int j)
		{
			int l=min(rk[i],rk[j])+1,r=max(rk[i],rk[j]);
			return Query(l,r);
		}
}A,B;
int main()
{
	for(int i=2;i<=30000;++i)lg[i]=lg[i>>1]+1;
	scanf("%d",&T);
	while(T--)
	{
		A.init();B.init();
		scanf("%s",s+1);
		n=strlen(s+1);
		for(int i=1;i<=n;++i)A.a[i]=s[i]-96;
		for(int i=1;i<=n;++i)B.a[n-i+1]=s[i]-96;
		A.GetSA();A.Pre();B.GetSA();B.Pre();
		for(int i=1;i<=n;++i)g[i]=f[i]=0;
		for(int len=1;len<=n/2;++len)
		{
			for(int i=len,j=i+len;j<=n;i+=len,j+=len)
			{
				int x=min(A.lcp(i,j),len);
				int y=min(B.lcp(n-i+2,n-j+2),len-1);
				int t=x+y-len+1;
				if(x+y>=len)
				{
					g[i-y]++;g[i-y+t]--;
					f[j+x-t]++;f[j+x]--;
				}
			}
		}
		
		for(int i=1;i<=n;++i)g[i]+=g[i-1];
		for(int i=1;i<=n;++i)f[i]+=f[i-1];
		ll ans=0;
		for(int i=1;i<n;++i)
			ans+=1ll*f[i]*g[i+1];
		printf("%lld\n",ans);
	}
	return 0;
}

posted @ 2018-01-26 10:03  小蒟蒻yyb  阅读(290)  评论(5编辑  收藏  举报