bzoj 3160
首先简化一下题意:
求一个字符串的子序列个数,要求这个子序列满足:是一个回文序列,且在原串中不连续
怎么搞?
设这个字符串为S
首先上一个容斥:我们找出所有回文子序列,然后减去连续的部分即可
而连续的部分可以用manacher算出来
所以我们重点研究一下如何找出所有回文子序列
首先我们回到manacher算法的思想:如果我想找出回文子序列,那么我们不如去找对称轴!(在接下来的介绍中不区分对称轴和对称中心)
而同理,对称轴会有两种情况:以一个字符为对称中心的和以一个空位为对称中心的
所以我们分类讨论:
①.设我们以位置$i$上的字符为对称轴,那么如果我们能找到两个位置$x,y$,使得$x+y=2*i$,且$S_x==S_y$,那么很显然,选出$(S_x,S_i,S_y)$这一个子序列是合法的
那么,如果我们能找到k组这样的x,y,那么以位置i上字符为对称轴的方案就是$2^(k+1)-1$
这一点很好理解,因为算上中间的i,一共有$k+1$组可以支持选或不选,但是要减去所有都不选的情况
②.设我们以位置$i$与$i+1$之间的空格为对称轴,那么如果我们能找到两个位置$x,y$,满足$x+y=2*i+1$,且$S_x==S_y$,那么很显然,选出$(S_x,S_y)$这一个子序列是合法的
那么,如果我们能找到k组这样的x,y,那么以位置i与i+1之间空格为对称轴的方案就是$2^k-1$
我们看到,设字符串长度为$n$,如果有$S_x==S_y$且对称轴为i(假设以一个字符对称),那么一定有$S_x==S_2*i-x$!
那么,由于只有a,b两种字符,所以我们可以分开匹配a,b两种字符
在匹配a字符时,我们可以设所有为a的位置值为1,而所有为b的位置值为0,那么可以发现,如果$S_x*S_2*i-x==1$,那么就可以说明这两个位置上是对应的a!
那么这是不是有点类似卷积的形式了?
可以发现,如果我们按上述说法将字符串转成一个序列,然后对这个序列自己与自己卷积,会得到一个长度为2*n的序列,其中下标为奇数的就是初始对称轴为空格的情况,下标为偶数的就是初始对称轴为一个字符的情况
那么做法是不就很显然了?
我们按a对字符串转成一个序列与自己卷积,记录对应位置的答案,再按b对字符串转成一个序列与自己卷积,将对应位置的答案累计上去,然后每一位分奇偶算2的幂次-1,最后减掉manacher做出的连续部分即可
注意在累计时,由于每一组对应的$S_x$与$S_2*i-x$在卷积时事实上被记录了两次,所以每一个位置都应该除2再做2的幂次!!
贴代码:
#include <cstdio> #include <cmath> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #include <queue> #include <stack> #define ll long long using namespace std; const ll mode=1000000007; const double pi=acos(-1.0); int to[(1<<20)+5]; struct cp { double x,y; }; cp operator + (cp a,cp b) { return (cp){a.x+b.x,a.y+b.y}; } cp operator - (cp a,cp b) { return (cp){a.x-b.x,a.y-b.y}; } cp operator * (cp a,cp b) { return (cp){a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x}; } void FFT(cp *a,int len,int k) { for(int i=0;i<len;i++)if(i<to[i])swap(a[i],a[to[i]]); for(int i=1;i<len;i<<=1) { cp w0=(cp){cos(pi/i),k*sin(pi/i)}; for(int j=0;j<len;j+=(i<<1)) { cp w=(cp){1,0}; for(int o=0;o<i;o++,w=w*w0) { cp w1=a[j+o]; cp w2=a[j+o+i]*w; a[j+o]=w1+w2; a[j+o+i]=w1-w2; } } } } char ch[100005]; char temp[200005]; int p[200005]; ll f[200005]; int n,maxp,p0; ll sum=0; int lim=1,l; void manacher() { temp[0]=temp[1]='#'; for(int i=1;i<=n;i++)temp[i<<1]=ch[i],temp[(i<<1)|1]='#'; p[0]=1; int l=(n<<1)+2; temp[l]='0'; for(int i=1;i<l;i++) { if(i<maxp)p[i]=min(p[(p0<<1)-i],p[p0]+p0-i); else p[i]=1; while(temp[i-p[i]]==temp[i+p[i]])p[i]++; if(p[i]+i>maxp)maxp=p[i]+i,p0=i; } for(int i=1;i<=l;i++)sum+=p[i]/2; } cp a[(1<<20)+5],b[(1<<20)+5],c[(1<<20)+5]; ll pow_mul(ll x,ll y) { ll ans=1; while(y) { if(y&1)ans*=x,ans%=mode; x*=x,x%=mode,y>>=1; } return ans; } int main() { scanf("%s",ch+1); n=strlen(ch+1); manacher(); for(int i=1;i<=n;i++)a[i-1].x=(ch[i]=='a'); while(lim<=2*n)lim<<=1,l++; for(int i=1;i<lim;i++)to[i]=((to[i>>1]>>1)|((i&1)<<(l-1))); FFT(a,lim,1); for(int i=0;i<lim;i++)c[i]=a[i]*a[i]; FFT(c,lim,-1); for(int i=0;i<2*n-1;i++)f[i]+=(ll)(c[i].x/lim+0.5); for(int i=1;i<=n;i++)b[i-1].x=(ch[i]=='b'); FFT(b,lim,1); for(int i=0;i<lim;i++)c[i]=b[i]*b[i]; FFT(c,lim,-1); for(int i=0;i<2*n-1;i++)f[i]+=(ll)(c[i].x/lim+0.5); ll ans=0; for(int i=0;i<2*n-1;i++) { if(i&1)ans+=pow_mul(2,f[i]/2)-1,ans%=mode; else ans+=pow_mul(2,f[i]/2+1)-1,ans%=mode; } printf("%lld\n",((ans-sum)%mode+mode)%mode); return 0; }