[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;
}

浙公网安备 33010602011771号