BZOJ 3160 万径人踪灭 FFT+Manacher

题意:链接

方法: FFT+Manacher

解析:

对于一个序列,求以任意位置(可以为间隙)为轴对称的不连续回文序列。

我们不妨举一个栗子。

这里写图片描述

然后我们发现,如果以图中的红线为对称轴的话,那么他的最长回文长度为3,也就是可非连续回文半径为3。

所以大情况下这个对答案的贡献是

C13+C23+C33=231

但是其中如图所示有一些不合法的选取。

也就是对于轴来说我们选取了连续,无断点的一段。

我们要把这部分减掉。

然而这部分恰好是以红线为轴的最长连续回文串的半径。

捋一下思路。

首先我们求出所有的可非连续回文半径长度,设 Xi (中间点也计算在其内)。

则对答案贡献为 2Xi1

接下里减去不合法的选取。

即所有的连续回文半径长度(中间点也计算在其内)。

然后考虑做法。

由于n的范围是10^5

所以n^2暴力找是不可取的。

如果把序列中的a看做1,b看做0。

则不妨举一下样例。

1 0 1 1 0 1 1

1 0 1 1 0 1 1

我们发现其实这个可非连续回文半径长度就是一个卷积的形式。

1x0+0x1+1x2+1x3+0x4+1x5+1x6

1x0+0x1+1x2+1x3+0x4+1x5+1x6

两个多项式乘在一起。

xk 前的系数恰好为以第k个位置(包括间隙,从0开始)为对称轴而对称的a的个数。

这个东西显然用FFT可以在O(nlogn)内求解。

但是此时我们没有算b。

所以还需要将b看做1,a看做0,求一遍乘积。

这样我们就完成了第一部分。

而第二部分,显然Manacher裸题2333333,O(n)求即可。

所以这道题就可以在O(nlogn)的复杂度下解决辣。

代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 100010
#define M 262144
#define pi acos(-1) 
#define mod 1000000007
using namespace std;
typedef long long ll;
int n,m,L;
char s[N];
char ss[N<<1];
struct complex
{
    double r,i;
    complex(double x=0.0,double y=0.0){r=x,i=y;}
    complex operator + (const complex a)
    {return complex(r+a.r,i+a.i);}
    complex operator - (const complex a)
    {return complex(r-a.r,i-a.i);}
    complex operator * (const complex a)
    {return complex(r*a.r-i*a.i,r*a.i+i*a.r);} 
}a[M],b[M];
int rev[M];
void FFT(complex *a,int f)
{
    for(int i=0;i<n;i++)if(i<rev[i])swap(a[i],a[rev[i]]);
    for(int h=2;h<=n;h<<=1)
    {
        complex wn(cos(2*pi*f/h),sin(2*pi*f/h));
        for(int i=0;i<n;i+=h)
        {
            complex w(1,0);
            for(int j=0;j<(h>>1);j++,w=w*wn)
            {
                complex t=w*a[i+j+(h>>1)];
                a[i+j+(h>>1)]=a[i+j]-t;
                a[i+j]=a[i+j]+t;
            }
        }
    }
    if(f==-1)for(int i=0;i<n;i++)a[i].r/=n;
}
int p[N<<1];
int powtwo[N<<1];
void pre()
{
    n=strlen(s+1);
    ss[0]='&',ss[1]='^';
    for(int i=1;i<=n;i++)
    {
        ss[i<<1]=s[i];
        ss[i<<1|1]='^';
    }
    n++,n<<=1;
}
void manacher()
{
    int ret=0,mx=0,id=0;
    for(int i=1;i<n;i++)
    {
        if(mx>i)p[i]=min(p[2*id-i],mx-i);
        else p[i]=1;
        while(ss[i-p[i]]==ss[i+p[i]])p[i]++;
        if(mx<p[i]+i)mx=p[i]+i,id=i;
    }
}
void init()
{
    powtwo[0]=1;
    for(int i=1;i<=2*n;i++)
        powtwo[i]=(powtwo[i-1]<<1)%mod;
}
int ans;
int f[N<<1];
int main()
{
    scanf("%s",s+1);
    n=strlen(s+1);
    n--;
    int nn=n;
    init();
    for(int i=0;i<=n;i++)
    {
        a[i].r=(s[i+1]=='a')?1:0;
        b[i].r=a[i].r;
        a[i].i=b[i].i=0;
    }
    m=2*n,L=0;
    for(n=1;n<=m;n<<=1)L++;
    for(int i=0;i<n;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<(L-1));
    FFT(a,1),FFT(b,1);
    for(int i=0;i<=n;i++)a[i]=a[i]*b[i];
    FFT(a,-1);
    for(int i=0;i<=m;i++)
    {
        int tmp=(int)(a[i].r+0.1);
        f[i]=f[i]+tmp;
    }
    m=2*nn,L=0;
    for(n=1;n<=m;n<<=1)L++;
    for(int i=0;i<n;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<(L-1));
    memset(a,0,sizeof(a));
    memset(b,0,sizeof(b));
    for(int i=0;i<=nn;i++)
    {
        a[i].r=(s[i+1]=='b')?1:0;
        b[i].r=a[i].r;
        a[i].i=b[i].i=0;
    }
    FFT(a,1),FFT(b,1);
    for(int i=0;i<=n;i++)a[i]=a[i]*b[i];
    FFT(a,-1);
    for(int i=0;i<=m;i++)
    {
        int tmp=(int)(a[i].r+0.1);
        f[i]=f[i]+tmp;
    }
    for(int i=0;i<=m;i++)
    {
        int tmp=(f[i]+1)/2;
        if(tmp>=0)
        ans=(((ans+powtwo[tmp])%mod-1)%mod+mod)%mod; 
    }
    pre();
    manacher();
    for(int i=0;i<=n;i++)ans=((ans-p[i]/2)%mod+mod)%mod;
    printf("%d\n",ans);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值