【BZOJ3160】万径人踪灭(FFT,Manacher)

【BZOJ3160】万径人踪灭(FFT,Manacher)

题面

BZOJ

题解

很容易想到就是满足条件的子序列个数减去回文子串的个数吧。。。

至于满足条件的子序列
我们可以依次枚举对称轴
如果知道关于这个位置对称的位置的组数
就很容易算了(直接\(2^k-1\)

而关于这个位置对称是什么东西?
\(s[x-i]=s[x+i]\)
也就是说,如果两个位置关于\(x\)位置对称,那么
\((x-i)+(x+i)=2x\)
也就是两个位置的坐标的和等于\(2x\)
这个玩意不就像一个卷积卷积了?

那么,\(a,b\)分开考虑
如果这个位置上有相应的字母就是\(1\)
如果两个位置都有\(1\)
那么,卷积\(x+y\)位置上就会有\(1\)
这样就可以算出前面要求的东西了

至于回文子串的个数?
\(Manacher\)

滑稽呀,马拉车写挂,身败名裂

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<complex>
using namespace std;
#define ll long long
#define RG register
#define MAX 500000
#define MOD 1000000007
const double Pi=acos(-1);
inline int read()
{
    RG int x=0,t=1;RG char ch=getchar();
    while((ch<'0'||ch>'9')&&ch!='-')ch=getchar();
    if(ch=='-')t=-1,ch=getchar();
    while(ch<='9'&&ch>='0')x=x*10+ch-48,ch=getchar();
    return x*t;
}
int len;
char s[MAX];
int p[MAX],N,M;
ll Manacher()
{
    s[len+len+1]='#';s[0]='-';
    for(int i=len;i;--i)
    {
        s[i*2]=s[i];
        s[i*2-1]='#';
    }
    int mx=0,id=0;
    for(int i=1;i<=len+len;++i)
    {
        p[i]=mx>i?min(p[id*2-i],mx-i):1;
        while(s[i+p[i]]==s[i-p[i]])++p[i];
        if(i+p[i]>mx)id=i,mx=p[i]+i;
    }
    ll ret=0;
    for(int i=1;i<=len+len;++i)ret=(ret+p[i]/2)%MOD;
    return ret;
}
int r[MAX],l;
complex<double> a[MAX],b[MAX];
ll f[MAX],tw[MAX],ans=0;
void FFT(complex<double> *P,int opt)
{
    for(int i=0;i<N;++i)if(i<r[i])swap(P[i],P[r[i]]);
    for(int i=1;i<N;i<<=1)
    {
        complex<double> W(cos(Pi/i),opt*sin(Pi/i));
        for(int p=i<<1,j=0;j<N;j+=p)
        {
            complex<double> w(1,0);
            for(int k=0;k<i;++k,w*=W)
            {
                complex<double> X=P[j+k],Y=P[i+j+k]*w;;
                P[j+k]=X+Y;P[i+j+k]=X-Y;
            }
        }
    }
}
void Work(char cc)
{
    memset(a,0,sizeof(a));
    memset(b,0,sizeof(b));
    for(int i=1;i<=len;++i)a[i]=b[i]=s[i]==cc;
    FFT(a,1);FFT(b,1);
    for(int i=0;i<N;++i)a[i]*=b[i];
    FFT(a,-1);
    for(int i=1;i<N;++i)a[i].real()=a[i].real()/N+0.5;
    for(int i=0;i<=M;++i)f[i]+=((int)(a[i].real())+1)/2;
}
int main()
{
    scanf("%s",s+1);
    len=strlen(s+1);
    M=len+len;
    tw[0]=1;
    for(int i=1;i<=M;++i)tw[i]=(tw[i-1]+tw[i-1])%MOD;
    for(N=1;N<=M;N<<=1)++l;
    for(int i=0;i<N;++i)r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));
    Work('a');Work('b');
    for(int i=1;i<=M;++i)ans=(ans+tw[f[i]]-1)%MOD;
    ans=(ans-Manacher()+MOD)%MOD;
    printf("%lld\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/cjyyb/p/8435460.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值