NOI2016 优秀的拆分
优秀的拆分
如果一个字符串可以被拆分为 \(\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\),我们需要求出,在它所有子串的所有拆分方式中,优秀拆分的总个数。这里的子串是指字符串中连续的一段。
以下事项需要注意:
- 出现在不同位置的相同子串,我们认为是不同的子串,它们的优秀拆分均会被记入答案。
- 在一个拆分中,允许出现 \(\text{A}=\text{B}\)。例如 \(\texttt{cccc}\) 存在拆分 \(\text{A}=\text{B}=\texttt{c}\)。
- 字符串本身也是它的一个子串。
对于全部的测试点,\(1 \leq T \leq 10, \ n \leq 30000\)。
题解
等价于对每个 \(i\) 求出 \(Pre_i\) 和 \(Suf_i\),表示 \(S[1 : i]\) 的 AA 型后缀个数以及 \(S[i : |S|]\) 的 AA 型前缀个数,那么答案显然就是 \(\sum^n_{i=1} Pre_iSuf_{i+1}\)
枚举一下 |A|,把 S 按 |A| 切段,对每两段去讨论一下,可以发现只要求 lcp 就行了
https://blog.csdn.net/qq_40512553/article/details/78915344
时间复杂度 \(O(n \ln n \log n)\)。
CO int N=3e4+10;
CO uint128 B=131;
uint128 pw[N],val[N];
char str[N];
int pre[N],suf[N];
IN uint128 calc(int l,int r){
return val[r]-val[l-1]*pw[r-l+1];
}
void real_main(){
scanf("%s",str+1);
int n=strlen(str+1);
for(int i=1;i<=n;++i) val[i]=val[i-1]*B+str[i];
fill(pre+1,pre+n+1,0),fill(suf+1,suf+n+1,0);
for(int L=1;2*L<=n;++L)for(int i=L+L;i<=n;i+=L){
int p=i-L;
if(str[i]!=str[p]) continue;
int lcs=0;
{
int l=1,r=L;
while(l<r){
int mid=(l+r+1)>>1;
if(calc(p-mid+1,p)==calc(i-mid+1,i)) l=mid;
else r=mid-1;
}
lcs=l;
}
int lcp=0;
{
int l=1,r=min(L,n-i+1);
while(l<r){
int mid=(l+r+1)>>1;
if(calc(p,p+mid-1)==calc(i,i+mid-1)) l=mid;
else r=mid-1;
}
lcp=l;
}
if(lcs+lcp>L){
++pre[i-lcs+L],--pre[i+lcp];
++suf[p-lcs+1],--suf[p+lcp-L+1];
}
}
for(int i=1;i<=n;++i) pre[i]+=pre[i-1],suf[i]+=suf[i-1];
int64 ans=0;
for(int i=1;i<n;++i) ans+=(int64)pre[i]*suf[i+1];
printf("%lld\n",ans);
}
int main(){
pw[0]=1;
for(int i=1;i<N;++i) pw[i]=pw[i-1]*B;
for(int T=read<int>();T--;) real_main();
return 0;
}
unsigned long long
会被卡,最后一个测试点的最后一组数据就是专门卡它的。