[BZOJ3160]万径人踪灭

bzoj
luogu

题意

就是要在一个只含\(a,b\)的字符串中选取一个子序列,使得:
1、位置和字符都关于某条对称轴对称。
2、不能是连续的一段。
求方案数模\(10^9+7,n\le10^5\)

sol

其实就是对于任意一个对称中心,你算出关于它对称的有多少位置,假设是\(f_i\)那么一个对称中心的贡献就是\(2^{f_i}-1\)(从中选取一个非空子集即可)。
但是这可能是连续的呀!
你想想连续的会是什么。回文串?
那么连续的个数其实就是字符串中回文串的个数!

到这里思路就比较清晰了,我们只需要求出“对于任意一个对称中心,有多少位置关于它对称”。
想一想两个字符关于某一个位置对称的形式化表示?
\(s[i]==s[2x-i]\)
其中\(x\)就是对称中心的位置,注意因为对称中心可以使某个夹缝,所以\(x\)是可以取\(k+0.5(k\in Z)\)的。
上面那个东西像什么?会发现两个下标之和\(i+(2x-i)=2x\)是一个常数,卷积?
对于每种字符\(ch\),设多项式\(f(x)\)其中\(f(i)=[s[i]==ch]\),我们只要对这个多项式卷积一下就可以求出每个位置有多少对满足对称关系的字符了。
因为在卷积中每对不同位置的对称字符被计算了两次,而相同位置的对称字符(自己和自己关于自身对称)只计算了一次,所以要把答案除2向上取整。

code

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const double Pi = acos(-1);
const int mod = 1e9+7;
const int N = 4e5+5;
struct Complex{
	double rl,im;
	Complex(){rl=im=0;}
	Complex(double a,double b){rl=a;im=b;}
	Complex operator + (Complex b)
		{return Complex(rl+b.rl,im+b.im);}
	Complex operator - (Complex b)
		{return Complex(rl-b.rl,im-b.im);}
	Complex operator * (Complex b)
		{return Complex(rl*b.rl-im*b.im,rl*b.im+im*b.rl);}
}w[N],a[N];
int n,m,l,rev[N],tw[N],f[N],p[N],mx,id,ans;
char s[N];
void FFT(Complex *P,int opt)
{
	for (int i=0;i<n;++i) if (i>rev[i]) swap(P[i],P[rev[i]]);
	for (int i=1;i<n;i<<=1)
		for (int p=i<<1,j=0;j<n;j+=p)
			for (int k=0;k<i;++k)
			{
				Complex W=w[n/i*k];W.im*=opt;
				Complex X=P[j+k],Y=W*P[j+k+i];
				P[j+k]=X+Y;P[j+k+i]=X-Y;
			}
	if (opt==-1) for (int i=0;i<n;++i) P[i].rl/=1.0*n;
}
void work(char ch)
{
	for (int i=0;i<n;++i) a[i]=Complex(s[i]==ch,0);
	FFT(a,1);
	for (int i=0;i<n;++i) a[i]=a[i]*a[i];
	FFT(a,-1);
	for (int i=2;i<=(m<<1);++i) f[i]+=((int)(a[i].rl+0.5)+1)/2;
}
int main()
{
	scanf("%s",s+1);m=strlen(s+1);
	tw[0]=1;
	for (int i=1;i<=m;++i) tw[i]=2ll*tw[i-1]%mod;
	for (n=1;n<=(m<<1);n<<=1) ++l;--l;
	for (int i=0;i<n;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<l);
	for (int i=0;i<n;++i) w[i]=Complex(cos(Pi*i/n),sin(Pi*i/n));
	work('a');work('b');
	for (int i=2;i<=(m<<1);++i)
		(ans+=tw[f[i]]-1)%=mod;
	s[0]='%';s[m<<1|1]='#';
	for (int i=m;i;--i)
		s[2*i]=s[i],s[2*i-1]='#';
	for (int i=1;i<=(m<<1);++i)
	{
		p[i]=mx>i?min(p[2*id-i],mx-i):1;
		while (s[i-p[i]]==s[i+p[i]]) ++p[i];
		if (i+p[i]>mx) mx=i+p[i],id=i;
	}
	for (int i=1;i<=(m<<1);++i)
		ans=(ans-p[i]/2+mod)%mod;
	printf("%d\n",ans);return 0;
}
posted @ 2018-03-29 22:10  租酥雨  阅读(208)  评论(0编辑  收藏  举报