[BZOJ3160]万径人踪灭 FFT+manacher

所求可以转化为所有位置和字母都回文的子序列减去回文子串。后者显然可以用manacher解决。
假设字符串倍增后,关于i对称的字母对一共有f[i]对,那么以其为对称中心的回文子序列就一共有2^f[i]-1种。我们还发现f[i]=∑[s[j]==s[i-j]]{1<=j<=i-1},s为未倍增的原串。这是一个卷积的形式,可以用fft解决,分成∑[s[j]==s[i-j]==’a’]和∑[s[j]==s[i-j]==’b’]两部分dft,结果相加在idft即可,(这样求出来的是2*f[i]-1,因为除了这一位本身被算了一次,其它的都被算了两次)。
代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<complex>
#define ll long long 
using namespace std;
typedef complex<double> cd;
const double PI=acos(-1);
const int mod=1000000007;
char cs[100010],s[200010];
int n,p[200010],r[280010];
cd f[280010],a[280010];
ll ksm(ll a,int b)
{
    ll re=1;
    while(b){if(b&1) re=re*a%mod;a=a*a%mod;b>>=1;}
    return re;
}
void manacher(int n)
{
    int rz=0,mxr=0;
    for(int i=1;i<=n;i++)
    {
        if(i>mxr) {p[i]=0;while(s[i-p[i]]==s[i+p[i]]) p[i]++;}
        else
        {
            if(p[2*rz-i]<mxr-i+1) {p[i]=p[2*rz-i];continue;}
            else {p[i]=mxr-i;while(s[i-p[i]]==s[i+p[i]]) p[i]++;}
        }
        rz=i;mxr=i+p[i]-1;
    }
}
void getrev(int bit)
{
    for(int i=0;i<(1<<bit);i++)
        r[i]=(r[i>>1]>>1)+((i&1)<<(bit-1));
}
void fft(cd *a,int n,int dft)
{
    for(int i=0;i<n;i++)
        if(i<r[i]) swap(a[i],a[r[i]]);
    for(int i=1;i<n;i<<=1)
    {
        cd wn=exp(cd(0,dft*PI/i));
        for(int j=0;j<n;j+=(i<<1))
        {
            cd wnk=cd(1,0);
            for(int k=j;k<i+j;k++)
            {
                cd x=a[k];
                cd y=wnk*a[k+i];
                a[k]=x+y;
                a[k+i]=x-y;
                wnk*=wn;
            }
        }
    }
    if(dft==-1) for(int i=0;i<n;i++) a[i]/=n;
}
int main()
{
    scanf("%s",cs+1);
    n=strlen(cs+1);
    for(int i=1;i<=n;i++)
        {s[2*i-1]='#';s[2*i]=cs[i];}
    s[0]='$';s[2*n+2]='&';s[2*n+1]='#'; 
    manacher(2*n+1);
    int bit=0;
    while((1<<bit)<2*n+1) bit++;
    getrev(bit);
    for(int i=0;i<(1<<bit);i++)
        if(i<=n&&cs[i]=='a') a[i]=1; else a[i]=0;

    fft(a,(1<<bit),1);
    for(int i=0;i<(1<<bit);i++)
        f[i]=a[i]*a[i];
    for(int i=0;i<(1<<bit);i++)
        if(i<=n&&cs[i]=='b') a[i]=1;else a[i]=0;

    fft(a,(1<<bit),1);

    for(int i=0;i<(1<<bit);i++)
        f[i]+=a[i]*a[i];
    fft(f,(1<<bit),-1);
    ll ans=0;
    for(int i=0;i<(1<<bit);i++)
        ans=(ans+ksm(2,((ll)(round(f[i].real()))+1)/2)-1+mod)%mod;
    for(int i=1;i<=2*n+1;i++)
        ans=(ans-p[i]/2+mod)%mod;           
    printf("%lld",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值