【洛谷P1117】优秀的拆分

题目

题目链接:https://www.luogu.com.cn/problem/P1117
如果一个字符串可以被拆分为 \(\text{AABB}\) 的形式,其中 \(\text{A}\)\(\text{B}\) 是任意非空字符串,则我们称该字符串的这种拆分是优秀的。
例如,对于字符串 $ \texttt{aabaabaa} $ ,如果令 \(\text{A}=\texttt{aab}\)\(\text{B}=\texttt{a}\),我们就找到了这个字符串拆分成 \(\text{AABB}\) 的一种方式。

一个字符串可能没有优秀的拆分,也可能存在不止一种优秀的拆分。
比如我们令 \(\text{A}=\texttt{a}\)\(\text{B}=\texttt{baa}\),也可以用 \(\text{AABB}\) 表示出上述字符串;但是,字符串 \(\texttt{abaabaa}\) 就没有优秀的拆分。

现在给出一个长度为 \(n\) 的字符串 \(S\),我们需要求出,在它所有子串的所有拆分方式中,优秀拆分的总个数。这里的子串是指字符串中连续的一段。

以下事项需要注意:

  1. 出现在不同位置的相同子串,我们认为是不同的子串,它们的优秀拆分均会被记入答案。
  2. 在一个拆分中,允许出现 \(\text{A}=\text{B}\)。例如 \(\texttt{cccc}\) 存在拆分 \(\text{A}=\text{B}=\texttt{c}\)
  3. 字符串本身也是它的一个子串。

思路

\(f[i]\) 表示以位置 \(i\) 开头的形如 AA 的字符串的数量,\(g[i]\) 表示以位置 \(i\) 结尾的形如 AA 的字符串数量。显然答案为 \(\sum^{n}_{i=1}g[i-1]f[i]\)
枚举形如 AA 的字符串中其中一半的长度 \(k\)。在原串上每 \(k\) 个位置设立一个关键点,那么显然任意一个长度为 \(2k\) 的形如 AA 的字符串都一定经过两个关键点。
考虑相邻的两个关键点 \(i,j(j=i+k)\) 会产生的贡献。我们求出以 \(i\)\(j\) 开头的两个后缀的 LCP,长度计为 \(l1\),再求出以 \(i-1\)\(j-1\) 结尾的两个前缀的 LCS,长度计为 \(l2\)
那么可以得到下图的信息:

显然如果 \(l1+l2<k\),那么无论如何不可能存在一个形如 AA 的长度为 \(k\) 的子串经过 \(i,j\)。否则一定存在 \(l1+l2-k+1\) 个这样的子串。例如上图中蓝色部分就是长度为 \(2\times 5\) 的开始位置,红色部分就是长度为 \(2\times 5\) 的结束位置。
我们可以通过这些信息计算出蓝色部分和红色部分,然后用差分来求出 \(f\)\(g\) 即可。
时间复杂度 \(O(n\log n)\)

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N=30010,LG=15;
int Q,n,m,x[N],y[N],c[N],f[N],g[N],lg[N],sa[2][N],rk[2][N],height[2][N],rmq[2][N][LG+1];
char s[N],t[N];
ll ans;
int Quant,Ask,my,dog;

void SA(char *s,int *sa)
{
	for (int i=1;i<=m;i++) c[i]=0;
	for (int i=1;i<=n;i++) x[i]=s[i],c[x[i]]++;
	for (int i=2;i<=m;i++) c[i]+=c[i-1];
	for (int i=n;i>=1;i--) sa[c[x[i]]--]=i;
	for (int k=1;k<=n;k<<=1)
	{
		int num=0;
		for (int i=n-k+1;i<=n;i++) y[++num]=i;
		for (int i=1;i<=n;i++) if (sa[i]>k) y[++num]=sa[i]-k;
		for (int i=1;i<=m;i++) c[i]=0;
		for (int i=1;i<=n;i++) c[x[i]]++;
		for (int i=2;i<=m;i++) c[i]+=c[i-1];
		for (int i=n;i>=1;i--) sa[c[x[y[i]]]--]=y[i],y[i]=0;
		swap(x,y);
		x[sa[1]]=num=1;
		for (int i=2;i<=n;i++)
			x[sa[i]]=(y[sa[i]]==y[sa[i-1]] && y[sa[i]+k]==y[sa[i-1]+k]) ? num : ++num;
		m=num;
		if (n==m) break;
	}
}

void geth(char *s,int id)
{
	for (int i=1;i<=n;i++) rk[id][sa[id][i]]=i;
	for (int i=1,k=0;i<=n;i++)
	{
		if (k) k--;
		int j=sa[id][rk[id][i]-1];
		while (s[i+k]==s[j+k]) k++;
		height[id][rk[id][i]]=k;
	}
}

void getst(int id)
{
	for (int i=1;i<=n;i++) rmq[id][i][0]=height[id][i];
	for (int j=1;j<=LG;j++)
		for (int i=1;i+(1<<j)-1<=n;i++)
			rmq[id][i][j]=min(rmq[id][i][j-1],rmq[id][i+(1<<(j-1))][j-1]);
}

int LCP(int id,int i,int j)
{
	i=rk[id][i]; j=rk[id][j];
	if (i>j) swap(i,j);
	int t=lg[j-i];
	return min(rmq[id][i+1][t],rmq[id][j-(1<<t)+1][t]);
}

void prework()
{
	memset(f,0,sizeof(f));
	memset(g,0,sizeof(g));
	memset(x,0,sizeof(x));
	memset(y,0,sizeof(y));
	memset(c,0,sizeof(c));
	memset(sa,0,sizeof(sa));
	memset(rk,0,sizeof(rk));
	memset(height,0,sizeof(height));
	memset(rmq,0,sizeof(rmq));
	ans=0;
}

int main()
{
	lg[1]=0;
	for (int i=2;i<N;i++)
		lg[i]=lg[i>>1]+1;
	scanf("%d",&Q);
	while (Q--)
	{
		prework();
		scanf("%s",s+1);
		n=strlen(s+1); m='z'+1;
		for (int i=1;i<=n;i++)
			t[i]=s[n-i+1];
		SA(s,sa[0]); geth(s,0); getst(0);
		SA(t,sa[1]); geth(t,1); getst(1);
		for (int k=1;k<=n;k++)
			for (int i=k;i+k<=n;i+=k)
			{
				int j=i+k,l1=min(LCP(0,i,j),k),l2=min(LCP(1,n-(i-1)+1,n-(j-1)+1),k-1);
				if (l1+l2<k) continue;
				int len=l2+l1-k+1;
				f[i-l2]++; f[i-l2+len]--;
				g[j+l1-len]++; g[j+l1]--;
			}
		for (int i=1;i<=n;i++)
		{
			f[i]+=f[i-1]; g[i]+=g[i-1];
			ans+=g[i-1]*f[i];
		}
		printf("%lld\n",ans);
	}
	return 0;
}
posted @ 2020-11-27 18:51  stoorz  阅读(323)  评论(0编辑  收藏  举报