BZOJ4259 - 残缺的字符串

Portal

Description

给出一个长度为\(m(m\leq3\times10^5)\)的字符串\(A\),一个长度为\(n(m\leq3\times10^5)\)的字符串\(B\),其中仅包含小写字母\(a\)~\(z\)和通配符\(*\)。求对于每个\(i\in[0,n-m]\)\(B_{i..i+m-1}\)是否能与\(A_{0..m-1}\)匹配。

Solution

没想到这居然是个FFT!
定义\(d(x)=\sum_{i=0}^{m-1} A_i B_{x+i}(A_i-B_{x+i})^2\),则\(d(x)=0 \Leftrightarrow B_{x..x+m-1}\)\(A_{0..m-1}\)匹配。将\(A\)翻转为\(A'\),令\(f(x)=d(x-m+1)\),则有:
\[\begin{align*} f(x)=d(x-m) &= \sum_{i=0}^{m-1} A'_{m-i-1} B_{x-m+1+i}(A'_{m-i}-B_{x-m+i})^2 \\ f(x) &= \sum_{i=0}^{m-1}A'_iB_{x-i}(A'_i-B_{x-i})^2 \\ f(x) &= \sum_{i=0}^{m-1}A'^3_iB_{x-i} - 2\sum_{i=0}^{m-1}A'^2_iB^2_{x-i} + \sum_{i=0}^{m-1}A'_iB^3_{x-i} \end{align*}\]用FFT计算所有的\(f(x)\)即可。注意频域加减等价于时域加减,所以只用做7遍FFT。

时间复杂度\(O(7nlogn)。\)

Code

std::complex太慢!要是想过这个题得手写complex

//残缺的字符串
#include <algorithm>
#include <complex>
#include <cstdio>
using std::swap;
typedef std::complex<double> cpx;
int const N=(1<<20)+10;
double const PI=acos(-1);
double const EPS=1e-5;
int n,m,a0[N],b0[N]; char s[N];
int t,pos[N]; cpx a[N],b[N],c[N];
void write(cpx x) {printf("(%.2lf %.2lf) ",x.real(),x.imag());}
bool equal0(cpx x) {return (int)(x.real()+0.5)==0;}
void FFT(cpx x[],int f)
{
    for(int i=0;i<t;i++) if(i<pos[i]) swap(x[i],x[pos[i]]);
    for(int i=1;i<t;i<<=1)
    {
        cpx Wn=cpx(cos(PI/i),f*sin(PI/i));
        for(int j=0;j<t;j+=i+i)
        {
            cpx w=1;
            for(int k=0;k<i;k++,w=w*Wn)
            {
                cpx p=x[j+k],q=w*x[j+k+i];
                x[j+k]=p+q,x[j+k+i]=p-q;
            }
        }
    }
    if(f==-1) for(int i=0;i<t;i++) x[i]/=t;
}
void convo(int k1,int k2)
{
    for(int i=0;i<t;i++) a[i]=pow(a0[i],k1);
    for(int i=0;i<t;i++) b[i]=pow(b0[i],k2);
    FFT(a,1),FFT(b,1);
    for(int i=0;i<t;i++) c[i]=a[i]*b[i];
}
cpx f[N]; int cnt,ans[N];
int main()
{
    scanf("%d%d",&m,&n);
    scanf("%s",s); for(int i=0;i<m;i++) a0[m-i-1]=s[i]^'*'?s[i]:0;
    scanf("%s",s); for(int i=0;i<n;i++) b0[i]=s[i]^'*'?s[i]:0;
    t=1; int n0=0; while(t<=n+m) t<<=1,n0++;
    for(int i=0;i<t;i++) pos[i]=(pos[i>>1]>>1)|((i&1)<<(n0-1));
    convo(3,1); for(int i=0;i<t;i++) f[i]+=c[i];
    convo(2,2); for(int i=0;i<t;i++) f[i]-=c[i]+c[i];
    convo(1,3); for(int i=0;i<t;i++) f[i]+=c[i];
    FFT(f,-1);
    int cnt=0;
    for(int i=m-1;i<n;i++) if(equal0(f[i])) ans[++cnt]=i-m+2; 
    printf("%d\n",cnt); if(!cnt) return 0;
    for(int i=1;i<=cnt;i++) printf("%d ",ans[i]);
    puts("");
    return 0;
}

P.S.

其实复杂度是\(7\times2^{20}\times20 \approx 1.4\times10^8\),本来就会T的。
不想手写complex,弃了弃了...

转载于:https://www.cnblogs.com/VisJiao/p/BZOJ4259.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值