bzoj4259 残缺的字符串(dp+FFT)

64 篇文章 0 订阅
16 篇文章 0 订阅

Description

很久很久以前,在你刚刚学习字符串匹配的时候,有两个仅包含小写字母的字符串A和B,其中A串长度为m,B串长度为n。可当你现在再次碰到这两个串时,这两个串已经老化了,每个串都有不同程度的残缺。
你想对这两个串重新进行匹配,其中A为模板串,那么现在问题来了,请回答,对于B的每一个位置i,从这个位置开始连续m个字符形成的子串是否可能与A串完全匹配?

Input

第一行包含两个正整数m,n(1<=m<=n<=300000),分别表示A串和B串的长度。
第二行为一个长度为m的字符串A。
第三行为一个长度为n的字符串B。
两个串均仅由小写字母和*号组成,其中*号表示相应位置已经残缺。

Output

第一行包含一个整数k,表示B串中可以完全匹配A串的位置个数。
若k>0,则第二行输出k个正整数,从小到大依次输出每个可以匹配的开头位置(下标从1开始)。

Sample Input

3 7
a*b
aebr*ob

Sample Output

2
1 5

HINT

Source

[ Submit][ Status][ Discuss]



分析:

毒瘤Claris!

我要写出最好的题解

首先我们要需要一个东西来判断两个字符串是否相等
dis(A,B) d i s ( A , B ) 表示字符串 A A B的差异程度(假设 A A B的长度相等为 n n

dis(A,B)=i=0n1(AiBi)2

由于字符串中存在通配字符,所以我们把 “ ∗ ” 的价值设为0,并且更改一下 dis d i s 的表达式:

dis(A,B)=n1i=0AiBi(AiBi)2 d i s ( A , B ) = ∑ i = 0 n − 1 A i B i ( A i − B i ) 2

由题目给出的数据范围可以看出, B B 串较长
所以我们可以枚举B串中匹配结束的点 i i
设计函数:

fi=dis(A,B[im+1,i])=j=0m1AjBim+1+j(AjBim+1+j)2
当且仅当 fi=0 f i = 0 时,两字符串相等

上面这个式子很像卷积,然而仅仅是像而已
我们能不能把ta构造成一个卷积的形式呢? 答案是肯定的

观察一下卷积的定义式: ck=ni=0aibki c k = ∑ i = 0 n a i b k − i
a,b a , b 的下标之和是一个定值

我们在字符串匹配的时候,从第一位开始:
这里写图片描述

如果我们把第二个字符串翻转一下:
这里写图片描述
就可以发现, A A B比对位置相加是一个定值,符合卷积的形式了

fi=dis(A,B[im+1,i])=m1j=0Am1jBim+1+j(Am1jBim+1+j)2 f i = d i s ( A , B [ i − m + 1 , i ] ) = ∑ j = 0 m − 1 A m − 1 − j B i − m + 1 + j ( A m − 1 − j − B i − m + 1 + j ) 2

B的下标实在太复杂了
为了简化下标,但是我们可以在A后面添加很多个0,让ta变成一个枚举i的式子
(意会一下就可以了)

fi=m1j=0AjBij(AjBij)2 f i = ∑ j = 0 m − 1 A j B i − j ( A j − B i − j ) 2

fi=m1j=0A3jBij2A2jB2ij+AjB3ij f i = ∑ j = 0 m − 1 A j 3 B i − j − 2 A j 2 B i − j 2 + A j B i − j 3

上式的三个部分都是卷积的形式
对于形如 A3j A j 3 的部分,直接 AjAjAj A j ∗ A j ∗ A j 即可(因为下标都相等,并不是卷积,只是最简单的乘法)

我们将三个部分分别计算卷积,相加即可

因为我们枚举的是 B B 串中匹配结束的点i
如果 fi=0 f i = 0 ,那么匹配起点是 im+1 i − m + 1

Q.

为什么卷积可以完成匹配的任务呢?
我们不是应该枚举 B B 的每一个字符吗?

A.

想一下卷积是干什么用的:多项式乘法
也就是说,AB B B 的每一个位置都会乘上A的第 i i
这就相当于以B的每一位置为匹配起点进行匹配

tip

数组的大小都是 fn f n (大于n+m最小二的整数幂)
对于 a a b的赋值不要偷懒只改 x x <script type="math/tex" id="MathJax-Element-6645">x</script>

#include<bits/stdc++.h>

using namespace std;

const int N=1000010;
const double pi=acos(-1.0);

struct node{
    double x,y;
    node (double xx=0,double yy=0) {
        x=xx;y=yy;
    }
};
node a[N],b[N],c[N],o[N],_o[N];
int n,m,fn,ans[N>>1];

node operator +(const node &a,const node &b) {return node(a.x+b.x,a.y+b.y);}
node operator -(const node &a,const node &b) {return node(a.x-b.x,a.y-b.y);}
node operator *(const node &a,const node &b) {return node(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}

void init(int n) {
    for (int i=0;i<n;i++) {
        o[i]=node(cos(2.0*i*pi/n),sin(2.0*i*pi/n));
        _o[i]=node(cos(2.0*i*pi/n),-sin(2.0*i*pi/n));
    }
}

void FFT(int n,node *a,node *w) {
    int i,j=0,k;
    for (i=0;i<n;i++) {
        if (i>j) swap(a[i],a[j]);
        for (int l=n>>1;(j^=l)<l;l>>=1);
    }
    for (i=2;i<=n;i<<=1) {
        int m=i>>1;
        for (j=0;j<n;j+=i) 
            for (k=0;k<m;k++) {
                node z=a[j+m+k]*w[n/i*k];
                a[j+m+k]=a[j+k]-z;
                a[j+k]=a[j+k]+z;
            }
    }
}

char A[300010],B[300010];
int s1[N],s2[N];

int get(char x) {
    if (x>='a'&&x<='z') return x-'a'+1;
    else return 0;
}

int main() {
    scanf("%d%d",&m,&n);
    scanf("%s",A);
    scanf("%s",B);
    for (int i=0;i<m;i++) s1[m-1-i]=get(A[i]);
    for (int i=0;i<n;i++) s2[i]=get(B[i]);

    fn=1;
    while (fn<=n+m) fn<<=1;
    init(fn);

    for (int i=0;i<fn;i++) a[i]=node(s1[i]*s1[i]*s1[i],0);
    for (int i=0;i<fn;i++) b[i]=node(s2[i],0);
    FFT(fn,a,o);
    FFT(fn,b,o);
    for (int i=0;i<fn;i++) c[i]=a[i]*b[i];

    for (int i=0;i<fn;i++) a[i]=node(2.0*s1[i]*s1[i],0);
    for (int i=0;i<fn;i++) b[i]=node(s2[i]*s2[i],0);
    FFT(fn,a,o);
    FFT(fn,b,o);
    for (int i=0;i<fn;i++) c[i]=c[i]-a[i]*b[i];

    for (int i=0;i<fn;i++) a[i]=node(s1[i],0);
    for (int i=0;i<fn;i++) b[i]=node(s2[i]*s2[i]*s2[i],0);
    FFT(fn,a,o);
    FFT(fn,b,o);
    for (int i=0;i<fn;i++) c[i]=c[i]+a[i]*b[i];

    FFT(fn,c,_o);
    int cnt=0;
    for (int i=0;i<=n-m;i++) 
        if (c[i+m-1].x/fn<0.5) ans[++cnt]=i+1;

    printf("%d\n",cnt);
    for (int i=1;i<=cnt;i++) printf("%d ",ans[i]);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值