题目链接:https://vjudge.net/problem/HDU-5785
题意:给定一个长为n的字符串(n<=1e6),求i*k的和,对1e9+7取模。其中[i,j]和[j+1,k]均是回文串。
思路:
- 首先用manacher算法处理得到每个点的回文半径p[i]。
- 然后用L[i]表示以i为回文串右边界的所有左边界的下标的和,R[i]表示以i为回文串左边界的所有右边界的下标的和。那么结果就是L[i]*R[i+1]的和,1<=i<=n-1。
- 然后就是求L[i],R[i]。拿L[i]来说,对于以i为中心的回文串,对所有 j (属于区间 [ i , i+p[i]-1 ])的L均有贡献,且贡献为i-(j-i) = 2*i-j,则L[j] =2*sigma(i) + k*j,其中i是将j包含在内的回文串的中心,k是这样的回文串的个数。同理,可以证明R[i]的表达式也是这样。
- 因为manacher算法之后字符串中插入了 '|' ,所以坐标扩大了两倍,所以实际上,L[j] = sigma(i) + k*(j/2),R[j]同理,对于对称轴是 '|' 的情况也成立。
- 代码中sub[1][i]表示以i为左边界的回文串的中心的和,sub[2][i]表示以i为左边界的回文串的个数,sub[3][i]表示以i为右边界的回文串的中心的和,sub[4][i]表示以i为右边界的回文串的个数。
- 枚举1到n-1,对于以i为中心的回文串,它对[i-p[i]+1 , i]的sub[1]贡献都是+i(通过差分数组sub[1][l] += i , sub[1][r+1] -= i实现),对sub[2]的贡献都是+1。同理对[i , i+p[i]-1]的sub[3]的贡献都是+i,对sub[4]的贡献都是+1。遍历之后,求差分数组的前缀和来得到相应的值,然后更新答案即可。
- 总复杂度O(n)。
(我最开始是想着以i为中心的回文串对[i , i+p[i]-1]的L的贡献是以i为首项,-1为公差的等差数列,于是就用二阶差分来做,但一直wa,QAQ...,也不知道哪错了)
AC code:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int maxn=1e6+5; typedef long long LL; const LL MOD=1e9+7; int n,p[maxn<<1]; LL sub[5][maxn<<1],ans; char ss[maxn],s[maxn<<1]; void manacher(){ int mid=0,r=0; for(int i=1;i<n;++i){ if(r>=i) p[i]=min(p[(mid<<1)-i],r-i+1); while(s[i-p[i]]==s[i+p[i]]) ++p[i]; if(i+p[i]>r) r=i+p[i]-1,mid=i; } } void add(int p,int l,int r,LL v){ sub[p][l]=(sub[p][l]+v+MOD)%MOD; sub[p][r+1]=(sub[p][r+1]-v+MOD)%MOD; } int main(){ while(~scanf("%s",ss)){ ans=0; n=strlen(ss); s[0]='~',s[1]='|'; for(int i=0;i<n;++i) s[2*i+2]=ss[i],s[2*i+3]='|'; s[2*n+2]=0; n=2*n+2; for(int i=0;i<=n;++i) p[i]=0,sub[1][i]=0,sub[2][i]=0,sub[3][i]=0,sub[4][i]=0; manacher(); for(int i=1;i<n;++i){ int t=p[i]-1; add(1,i-t,i,i); add(2,i-t,i,1); add(3,i,i+t,i); add(4,i,i+t,1); } for(int i=1;i<n;++i) for(int j=1;j<=4;++j) sub[j][i]=(sub[j][i]+sub[j][i-1]+MOD)%MOD; for(int i=2;i<n-2;i+=2){ LL aa=((sub[1][i+2]-sub[2][i+2]*((i+2)/2))%MOD+MOD)%MOD; LL bb=((sub[3][i]-sub[4][i]*(i/2))%MOD+MOD)%MOD; ans=(ans+aa*bb%MOD)%MOD; } printf("%lld ",ans); } return 0; }