[NOI2016]优秀的拆分

题目描述

如果一个字符串可以被拆分为\(AABB\)的形式,其中\(A\)\(B\)是任意非空字符串,则我们称该字符串的这种拆分是优秀的。
例如,对于字符串\(aabaabaa\),如果令\(A=aab\)\(B=a\),我们就找到了这个字符串拆分成\(AABB\)的一种方式。
一个字符串可能没有优秀的拆分,也可能存在不止一种优秀的拆分。比如我们令\(A=a\)\(B=baa\),也可以用\(AABB\)表示出上述字符串;但是,字符串\(abaabaa\)就没有优秀的拆分。
现在给出一个长度为\(n\)的字符串\(S\),我们需要求出,在它所有子串的所有拆分方式中,优秀拆分的总个数。这里的子串是指字符串中连续的一段。
以下事项需要注意:
\(1\). 出现在不同位置的相同子串,我们认为是不同的子串,它们的优秀拆分均会被记入答案。
\(2\). 在一个拆分中,允许出现\(A=B\)。例如\(cccc\)存在拆分\(A=B=c\)
\(3\). 字符串本身也是它的一个子串。


输入格式

第一行一个整数\(T\),表示数据的组数。保证\(1\le T\le 10\)
接下来\(T\)行,每行一个仅由英文小写字母构成的字符串\(S\),意义如题所述。


输出格式

输出\(T\)行,每行一个整数,表示字符串\(S\)所有子串的所有拆分中,总共有多少个是优秀的拆分。


输入输出样例

输入

4
aabbbb
cccccc
aabaabaabaa
bbaabaababaaba

输出

3
5
4
7

说明/提示

我们用\(S_{i,j}\)表示字符串\(S\)\(i\)个字符到第\(j\)个字符的子串(从\(1\)开始计数)。
第一组数据中,共有\(3\)个子串存在优秀的拆分:
\(S_{1,4}=aabb\),优秀的拆分为\(A=a\)\(B=b\)
\(S_{3,6}=bbbb\),优秀的拆分为\(A=b\)\(B=b\)
\(S_{1,6}=aabbbb\),优秀的拆分为\(A=a\)\(B=bb\)
而剩下的子串不存在优秀的拆分,所以第一组数据的答案是\(3\)
第二组数据中,有两类,总共\(4\)个子串存在优秀的拆分:
对于子串\(S_{1,4}=S_{2,5}=S_{3,6}=cccc\),它们优秀的拆分相同,均为\(A=c\)\(B=c\),但由于这些子串位置不同,因此要计算\(3\)次;
对于子串\(S_{1,6}=cccccc\),它优秀的拆分有\(2\)种:\(A=c\)\(B=cc\)\(A=cc\)\(B=c\),它们是相同子串的不同拆分,也都要计入答案。
所以第二组数据的答案是\(3+2=5\)
第三组数据中,\(S_{1,8}\)\(S_{4,11}\)各有\(2\)种优秀的拆分,其中\(S_{1,8}\)是问题描述中的例子,所以答案是\(2+2=4\)
第四组数据中,\(S_{1,4}\)\(S_{6,11}\)\(S_{7,12}\)\(S_{2,11}\)\(S_{1,8}\)各有 \(1\)种优秀的拆分,\(S_{3,14}\)\(2\)种优秀的拆分,所以答案是\(5+2=7\)
对于全部的测试点,保证\(1\le T\le 10\)。以下对数据的限制均是对于单组输入数据而言的,也就是说同一个测试点下的\(T\)组数据均满足限制条件。
我们假定\(n\)为字符串\(S\)的长度,每个测试点的详细数据范围见下表:



后缀数组+ST表+差分

分析

由于\(AABB\)是由\(AA\)\(BB\)组成的,我们可以先对\(AA\)进行考虑,然后再计算两个连在一起的\(AA\)(即\(AABB\))有多少个。
我们设\(f(i)\)表示最后一个字母位置为\(i\)\(AA\)有多少个,\(g(i)\)表示第一个字母位置为\(i\)\(AA\)有多少个,则最终答案为\(\sum_{i=1}^{n-1}f(i)\times g(i+1)\)
我们要枚举\(AA\)的个数,显然先要枚举\(AA\)的长度,即\(A\)的长度的两倍,因此枚举\(A\)的长度即可。为了使时间复杂度降到\(O(n\ log\ n)\),我们考虑这样的做法:

每次枚举一个长度\(length\),然后将\(S\)从头开始划分出\(\lfloor\frac{n}{length}\rfloor\)个连续的长度为\(length\)的区间。对于一个区间,设其第一个字母位置为\(i\),最后一个字母位置为\(j\),我们计算\(S_{1,i-1}\)\(S_{1,j}\)的最长公共后缀(设其长度为\(LCS\))以及\(S_{i,n}\)\(S_{j+1,n}\)的最长公共前缀(设其长度为\(LCP\))。若\(length>LCS+LCP\),显然不可能存在长度为\(2\times length\)\(AA\)包含该区间。相反的,若\(length\le LCS+LCP\),一定存在且仅存在\(LCS+LCP-length+1\)个长度为\(2\times length\)\(AA\)包含该区间,分别是\(S_{i-LCS,j+length-LCS},S_{i-LCS+1,j+length-LCS+1},\cdots,S_{i-length+LCP,j+LCP}\),对它们更新\(f\)值和\(g\)值即可。

后缀数组

求解两个字符串的\(LCP\)\(LCS\)(两个字符串反转之后的\(LCP\))等问题通常需要使用后缀数组。

ST表

我们先利用后缀数组计算\(S_{SA(i),n}\)\(S_{SA(i+1),n}\)\(LCP\)\(1\le i<n\)),然后根据倍增思想,使用ST表辅助计算\(S_{SA(i),n}\)\(S_{SA(j),n}\)\(LCP\)\(1\le i,j\le n,i\neq j\))。

差分

由于包含上述枚举的区间的\(AA\)是连续的,我们可以使用差分法对\(f\)值和\(g\)值进行更新。


代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN=(int)3e4+5,MAXM=15;
int sta[MAXN][MAXM],stb[MAXN][MAXM];
int raka[MAXN],rakb[MAXN],saa[MAXN],sab[MAXN],hgta[MAXN],hgtb[MAXN];
int lg[MAXN],sum[MAXN],sa[MAXN],rak[MAXN],f[MAXN],g[MAXN];
char S[MAXN];
int n;
inline void iit(int(*st)[MAXM],int*Rak,int*Sa,int*Hgt){
	int j=1,hgt=0;
	for(register int i=1;i<=n;i++)
		sum[S[i]-'f'+1]=1;
	for(register int i=2;i<=26;i++)
		sum[i]+=sum[i-1];
	for(register int i=1;i<=n;i++)
		Rak[i]=sum[S[i]-'f'+1];
	memset(sum,0,sizeof(sum));
	while(j<n){
		for(register int i=1;i<=n;i++)
			sum[Rak[i+j]]++;
		for(register int i=1;i<=n;i++)
			sum[i]+=sum[i-1];
		for(register int i=1;i<=n;i++)
			sa[sum[Rak[i+j]]--]=i;
		memset(sum,0,sizeof(sum));
		for(register int i=1;i<=n;i++)
			sum[Rak[i]]++;
		for(register int i=2;i<=n;i++)
			sum[i]+=sum[i-1];
		for(register int i=n;i;i--)
			Sa[sum[Rak[sa[i]]]--]=sa[i];
		for(register int i=1;i<=n;i++)
			rak[i]=Rak[i];
		for(register int i=2;i<=n;i++)
			if(!(Rak[Sa[i]]^Rak[Sa[i-1]])&&!(Rak[Sa[i]+j]^Rak[Sa[i-1]+j]))
				rak[Sa[i]]=rak[Sa[i-1]];
			else rak[Sa[i]]=rak[Sa[i-1]]+1;
		for(register int i=1;i<=n;i++)
			Rak[i]=rak[i];
		memset(sum,0,sizeof(sum));
		j<<=1;
	}
	for(register int i=1;i<=n;i++)
		Sa[Rak[i]]=i;
	for(register int i=1;i<=n;i++){
		if(hgt)
			hgt--;
		while(i+hgt<=n&&Sa[Rak[i]-1]+hgt<=n&&!(S[i+hgt]^S[Sa[Rak[i]-1]+hgt]))
			hgt++;
		Hgt[Rak[i]]=hgt;
	}
	for(register int i=1;i<=n;i++)
		st[i][0]=Hgt[i];
	for(register int i=1;1<<i<=n;i++)
		for(register int j=1;j+(1<<i)-1<=n;j++)
			st[j][i]=min(st[j][i-1],st[j+(1<<i-1)][i-1]);
}
inline int Lcp(int(*st)[MAXM],int lt,int rt){
	if(lt>rt)
		lt^=rt^=lt^=rt;
	return min(st[lt+1][lg[rt-lt]],st[rt-(1<<lg[rt-lt])+1][lg[rt-lt]]);
}
int main(){
	int t;
	scanf("%d",&t);
	for(register int i=2;i<=MAXN-5;i++)
		lg[i]=lg[i>>1]+1;
	while(t--){
		long long ans=0;
		scanf("%s",S+1);
		n=strlen(S+1);
		iit(sta,raka,saa,hgta);
		reverse(S+1,S+n+1);
		iit(stb,rakb,sab,hgtb);
		for(register int i=1;i<=n>>1;i++)
			for(register int j=i;j<=n;j+=i){
				int lcp=min(Lcp(sta,raka[j],raka[i+j]),i-1);
				int lcs=min(Lcp(stb,rakb[n-(i+j-1)+1],rakb[n-(j-1)+1]),i);
				if(lcp+lcs>=i){
					f[i+j-1+lcp-(lcp+lcs-i)]++;
					f[i+j+lcp]--;
					g[j-lcs]++;
					g[j-lcs+(lcp+lcs-i)+1]--;
				}
			}
		for(register int i=2;i<=n;i++){
			f[i]+=f[i-1];
			g[i]+=g[i-1];
		}
		for(register int i=1;i<n;i++)
			ans+=1ll*f[i]*g[i+1];
		printf("%lld\n",ans);
		memset(raka,0,sizeof(raka));
		memset(rakb,0,sizeof(rakb));
		memset(f,0,sizeof(f));
		memset(g,0,sizeof(g));
	}
	return 0;
}

posted @ 2019-10-19 20:26  坚如钻石  阅读(292)  评论(0)    收藏  举报