[NOI2016]优秀的拆分

题目链接:https://www.luogu.org/problemnew/show/P1117

后缀数组的题,根本不会。。看了题解学了一个很6的技巧。

首先这道题要求AABB,那么考虑分别求AA,BB,定义F(0,i)表示以i位置为开头的BB子串数量,F(1,i)表示以i位置为结尾的AA子串数量,若能快速求得这两个东西,则答案就是sigma F(1,i)*F(0,i+1),所以考虑如何求。

首先求AA,BB实际上是等价的,翻转一下就行了,所以就讨论F(0,i)即BB怎么求。

开始能想到一个n^2暴力做法,枚举B长度和起始位置。考虑优化,串长度还是要枚举的,然后就有个很6的操作了,在l,2l,3l......这些位置假装插一个点,那么对于总串长为2l的BB,它一定能覆盖到两个插入的点,所以对相邻的两点求最长前缀,最长后缀就能求出一个长度2l的BB串,起点在靠左的插入点左边的可行区间,打一个差分标记最后统计即可,求最长前缀可以用后缀数组和st表O(1)实现,总复杂度就是调和级数nlog(n)

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=3e4+100;

int n,lg[N],mi[25],F[2][N];
char s[N];

struct SA{
	int m,x[N],y[N],tax[N],rnk[N],sa[N],ht[N],st[N][20];
	void rsort()
	{
		for(int i=0;i<=m;i++)tax[i]=0;
		for(int i=1;i<=n;i++)tax[x[i]]++;
		for(int i=1;i<=m;i++)tax[i]+=tax[i-1];
		for(int i=n;i>=1;i--)sa[tax[x[y[i]]]--]=y[i];
	}
	void cal()
	{
		int num;
		for(int i=1;i<=n;i++)x[i]=s[i],y[i]=i;
		m=130,rsort();
		for(int l=1;;l<<=1)
		{
			num=0;
			for(int i=n-l+1;i<=n;i++)y[++num]=i;
			for(int i=1;i<=n;i++)
				if(sa[i]>l)y[++num]=sa[i]-l;
			rsort(),swap(x,y);
			num=x[sa[1]]=1;
			for(int i=2;i<=n;i++)
				x[sa[i]]=(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+l]==y[sa[i-1]+l])?num:++num;
			if(num==n)break;m=num;
		}
		for(int i=1;i<=n;i++)rnk[sa[i]]=i;
		int k=0;
		for(int i=1,j;i<=n;i++)
		{
			if(rnk[i]==1){ht[1]=k=0;continue;}
			if(k)--k;j=sa[rnk[i]-1];
			while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])++k;
			ht[rnk[i]]=k;
		}
		for(int i=1;i<=n;i++)st[i][0]=ht[i];
		for(int l=2,i=1;l<=n;l<<=1,i++)
			for(int j=1;j+l<=n+1;j++)
				st[j][i]=min(st[j][i-1],st[j+(l>>1)][i-1]);
	}
	int qry(int l,int r)
	{
		l=rnk[l],r=rnk[r];
		if(l>r)swap(l,r);++l;
		int tp=lg[r-l+1],md=mi[tp];
		return min(st[l][tp],st[r-md+1][tp]);
	}
	void clr(){memset(x,0,sizeof x),memset(y,0,sizeof y),memset(sa,0,sizeof sa);}
}sa1,sa2;

int cf[N];

void sol(int op)
{
	int x,y,tp;
	memset(cf,0,sizeof cf);
	for(int l=1;l+l<=n;l++)
	{
		for(int i=l,j=l+l;j<=n;i+=l,j+=l)
		{
			y=sa1.qry(i,j),x=sa2.qry(n-i+1,n-j+1);
			x=min(x,l),y=min(y,l);
			if(x+y<=l)continue;
			cf[i-x+1]++,tp=l-y,cf[i-tp+1]--;
		}
	}
	for(int i=1;i<=n;i++)cf[i]+=cf[i-1],F[op][i]=cf[i];
}

int main()
{
	int T;long long ans;
	lg[1]=0,mi[0]=1;
	for(int i=2;i<N;i++)lg[i]=lg[i>>1]+1;
	for(int i=1;i<=20;i++)mi[i]=mi[i-1]*2;
	scanf("%d",&T);
	while(T--)
	{
		ans=0,sa1.clr(),sa2.clr();
		scanf("%s",s+1),n=strlen(s+1);
		sa1.cal(),reverse(s+1,s+n+1),sa2.cal();
		sol(0),swap(sa1,sa2),sol(1),reverse(F[1]+1,F[1]+n+1);
		for(int i=2;i<n;i++)ans+=1LL*F[1][i]*F[0][i+1];
		printf("%lld\n",ans);
	}
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值