CF17E Palisection(字符串)
给定一个长度为 \(n\) \((n\le 2\times 10^6)\) 的小写字母串。问你有多少对相交的回文子串(包含也算相交)。
相交的回文子串,统计起来是很麻烦的,因为相交有 \(8\) 个条件。所以要转化为总的减去不相交的。
看到“不相交”,想到这类问题有一个通常的方法:对于每个 \(i\),求出以 \(i\) 结尾的(回文)子串数 \(l_i\) 和以 \(i\) 开头的(回文)子串数 \(r_i\)。\(O(n)\) 跑一遍,统计出“前面的乘后面的个数”,就是不相交的。(感性理解一下。)
形式化的,答案可以记作 \(\sum_{i=1}^n r_i \times \sum_{j=i}^{i-1} l_j\)。
求 \(l_i,\,r_i\) 和总子串对数用到 manacher 算法所求的回文半径,实现上运用差分实现 \(O(1)\) 区间加。
下面是 AC 代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e6+10;
const int P=51123987;
char a[N],s[N<<1];
ll n,m=1,mx,mid,ans,sum,p[N<<1],l[N<<1],r[N<<1];
int main(){
scanf("%I64d%s",&n,a),s[0]='$';
for(int i=0;i<n;++i,++m) s[++m]=a[i];
for(int i=1;i<=m;++i){
p[i]=mx>i?min(p[mid*2-i],mx-i):1;
while(i+p[i]<=m&&i-p[i]>0&&s[i+p[i]]==s[i-p[i]]) ++p[i];
if(i+p[i]>mx) mx=i+p[i],mid=i;
(ans+=p[i]/2)%=P;
}
ans=(ans-1)*ans/2%P;
for(int i=1;i<=m;++i){
++l[i-p[i]+1],--l[i+1];
++r[i],--r[i+p[i]];
}
for(int i=1;i<=m;++i){
l[i]+=l[i-1],r[i]+=r[i-1];
if(i%2==0) (ans-=1ll*sum*l[i]%P)%=P,(sum+=r[i])%=P;
}
printf("%I64d\n",(ans+P)%P);
return 0;
}