bzoj4650: [Noi2016]优秀的拆分

5 篇文章 0 订阅
4 篇文章 0 订阅

此题有95分暴力。。。

发现AABB的统计相当于对AA的统计。于是可以计算出每个点左侧为AA的方案数。枚举|A|,将字符串分成长度为|A|的小段,当AA中点在小段中的情况可以利用SA+rmq,O(1)求。于是就能在O(n/1+n/2+...+n/n)=O(nlogn)内求解。

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 80005
using namespace std;
int n,T,A[N],B[N],lg[N],bit[20];long long Ans;
struct SuffixArray
{
	char a[N];
	int SA[N],Rk[N],Ht[N],X[N],Y[N],v[N],Min[N][20];
	void GetSA(int n,int m=256)
	{
		int *x=X,*y=Y,i,p,j;
		memset(v,0,sizeof v);
		for(i=1;i<=n;i++)v[x[i]=a[i]]++;
		for(i=1;i<=m;i++)v[i]+=v[i-1];
		for(i=n;i>=1;i--)SA[v[x[i]]--]=i;
		for(i=1;i<=n;i<<=1,m=p)
		{
			p=0;
			memset(v,0,sizeof v);
			for(j=n-i+1;j<=n;j++)y[++p]=j;
			for(j=1;j<=n;j++)if(SA[j]>i)y[++p]=SA[j]-i;
			for(j=1;j<=n;j++)v[x[y[j]]]++;
			for(j=1;j<=m;j++)v[j]+=v[j-1];
			for(j=n;j>=1;j--)SA[v[x[y[j]]]--]=y[j];
			swap(x,y);
			p=1;x[SA[1]]=1;
			for(j=2;j<=n;j++)
				if(y[SA[j-1]]==y[SA[j]]&&y[SA[j-1]+i]==y[SA[j]+i]) x[SA[j]]=p;
					else x[SA[j]]=++p;
			if(p>=n)break;
		}
	}
	void GetHt(int n)
	{
		int k=0;
		for(int i=1;i<=n;i++)Rk[SA[i]]=i;
		for(int i=1;i<=n;i++)
		{
			if(k)k--;
			int j=SA[Rk[i]-1];
			while(i+k<=n&&j+k<=n&&a[i+k]==a[j+k])k++;
			Ht[Rk[i]]=k;
		}
	}
	void Get(int n)
	{
		memset(SA,0,sizeof SA);
		memset(Ht,0,sizeof Ht);
		memset(Rk,0,sizeof Rk);
		memset(X,0,sizeof X);
		memset(Y,0,sizeof Y);
		memset(Min,0,sizeof Min);
		
		GetSA(n);GetHt(n);
		for (int i=1;i<=n;i++)
			Min[i][0]=Ht[i];
		for (int i=1;i<=15;i++)
			for (int j=1;j<=n;j++)
				Min[j][i]=min(Min[j][i-1],Min[j+bit[i-1]][i-1]);
	}
	
	int lcp(int x,int y)
	{
		if (Rk[x]>Rk[y]) swap(x,y);		
		x=Rk[x];y=Rk[y];
		int t=lg[y-x];
		return min(Min[x+1][t],Min[y-bit[t]+1][t]);
	}
}S1,S2;
int main()
{
	bit[0]=1;
	for (int i=1;i<=15;i++) bit[i]=bit[i-1]<<1;
	for (int i=0,j=1,next=2;i<=15;i++,next<<=1)
		for (;j<=next;j++) lg[j]=i;
	scanf("%d",&T);
	while(T--)
	{
		Ans=0;
		memset(A,0,sizeof A);
		memset(B,0,sizeof B);
		
		scanf("%s",S1.a+1);n=strlen(S1.a+1);
		for (int i=1;i<=n;i++) S2.a[n-i+1]=S1.a[i];
		S1.Get(n);
		S2.Get(n);
		for (int i=1;i<=n/2;i++)
		{
			for (int j=1;j+i<=n;j+=i)
			{
				int x=S1.lcp(j,j+i),y=S2.lcp(n-j+1,n-j-i+1);
				int l=max(j,j-y+i),r=min(j+i,j+x)-1;
				if (l<=r)
				{
					A[l+i]++;A[r+i+1]--;
					B[l-i]++;B[r-i+1]--;
				}
			}
		}
		for (int i=1;i<=n;i++)
			A[i]+=A[i-1],B[i]+=B[i-1];
		for (int i=1;i<=n;i++)
			Ans+=A[i]*B[i];
		printf("%lld\n",Ans);
	}
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值