bzoj4503 两个串(dp+FFT)

16 篇文章 0 订阅

Description

兔子们在玩两个串的游戏。给定两个字符串S和T,兔子们想知道T在S中出现了几次,分别在哪些位置出现。注意T中可能有“?”字符,这个字符可以匹配任何字符。

Input

两行两个字符串,分别代表S和T

Output

第一行一个正整数k,表示T在S中出现了几次
接下来k行正整数,分别代表T每次在S中出现的开始位置。按照从小到大的顺序输出,S下标从0开始。

Sample Input

bbabaababaaaaabaaaaaaaabaaabbbabaaabbabaabbbbabbbbbbabbaabbbababababbbbbbaaabaaabbbbbaabbbaabbbbabab

a?aba?abba

Sample Output

0

HINT

S 长度不超过 10^5, T 长度不会超过 S。 S 中只包含小写字母, T中只包含小写字母和“?”

Source

[ Submit][ Status][ Discuss]



分析:
我们定义两个长度均为n的字符串的距离(字符串的不同程度)为:

dis=n1i=0(s1[i]s2[i])2 d i s = ∑ i = 0 n − 1 ( s 1 [ i ] − s 2 [ i ] ) 2

因为 s2 s 2 中存在通配字符 ? “ ? ” ,所以我们把 ? “ ? ” 的值设为0,这样距离的计算公式就变成了:

dis=n1i=0s1[i]s2[i](s1[i]s2[i])2 d i s = ∑ i = 0 n − 1 s 1 [ i ] s 2 [ i ] ∗ ( s 1 [ i ] − s 2 [ i ] ) 2
当且仅当dis=0的时候,两字符串相等

事先声明一下:这道题需要用FFT优化
所以我们需要寻找卷积的形式

因为题目给出的S和T的长度不一样,我们可以枚举第一个串匹配结束位置
定义函数:

fk=m1i=0S[km+1+i]T[i](S[km+1+i]T[i])2 f k = ∑ i = 0 m − 1 S [ k − m + 1 + i ] T [ i ] ∗ ( S [ k − m + 1 + i ] − T [ i ] ) 2

其中,第一个串匹配结束位置为k,m为第二串的长度
当且仅当 fk=0 f k = 0 的时候表明匹配成功了

那么我们就从第一位开始比对
这里写图片描述
我们观察一下卷积的形式: Cn=n1i=0aibni C n = ∑ i = 0 n − 1 a i b n − i
可以看到,a和b的下标之和是一个定值
我们可以把T翻转:
这里写图片描述
这样S和T比对位置就符合卷积的形式了:

fk=m1i=0S[km+1+i]T[m1i](S[km+1+i]T[m1i])2 f k = ∑ i = 0 m − 1 S [ k − m + 1 + i ] T [ m − 1 − i ] ∗ ( S [ k − m + 1 + i ] − T [ m − 1 − i ] ) 2

S的下标实在太复杂了
为了简化下标,但是我们可以在 T T 后面添加很多个0,让ta变成一个枚举k的式子

fk=ki=0S[i]T[ki](S[i]T[ki])2 f k = ∑ i = 0 k S [ i ] T [ k − i ] ∗ ( S [ i ] − T [ k − i ] ) 2

fk=n1i=0S[i]3T[ki]2S[i]2T[ki]2+S[i]T[ki]3 f k = ∑ i = 0 n − 1 S [ i ] 3 T [ k − i ] − 2 ∗ S [ i ] 2 T [ k − i ] 2 + S [ i ] T [ k − i ] 3

因为只有T中有 ? “ ? ” ,所以我们可以少乘一个S[i]

fk=n1i=0S[i]2T[ki]2S[i]T[ki]2+T[ki]3 f k = ∑ i = 0 n − 1 S [ i ] 2 T [ k − i ] − 2 ∗ S [ i ] T [ k − i ] 2 + T [ k − i ] 3

我们针对每一部分计算卷积,相加即可

Q.

为什么卷积可以完成比对的任务呢?
我们不是应该以S的每一个字符为起点进行比对吗?

A.

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

tip

FFT只是用来计算这样的式子的: S[i]T[ni] S [ i ] T [ n − i ]
至于 S[i]2 S [ i ] 2 这样的,直接 S[i]S[i] S [ i ] ∗ S [ i ]
注意最后一部分 T[ni]3 T [ n − i ] 3 ,也是一个卷积形式: T[ni]31 T [ n − i ] 3 ∗ 1

注意我们枚举的是匹配结束的位置

为了保险,所有的范围都设为fn
对于a和b的赋值不要偷懒只改x

#include<bits/stdc++.h>

using namespace std;

const double pi=acos(-1.0);
const int N=500005;
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,s[N],t[N],ans[N];

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 S[N>>2],T[N>>2];

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

int main() 
{
    scanf("%s",S);
    scanf("%s",T);
    n=strlen(S); 
    m=strlen(T);
    for (int i=0;i<n;i++) s[i]=get(S[i]);
    for (int i=0;i<m;i++) t[m-i-1]=get(T[i]);

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

    for (int i=0;i<fn;i++) a[i]=node(s[i]*s[i],0);
    for (int i=0;i<fn;i++) b[i]=node(t[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*s[i],0);
    for (int i=0;i<fn;i++) b[i]=node(t[i]*t[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(1.0,0);
    for (int i=0;i<fn;i++) b[i]=node(t[i]*t[i]*t[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);
    ans[0]=0;
    for (int i=0;i<=n-m;i++) 
        if (c[i+m-1].x/fn<0.5) ans[++ans[0]]=i;     //i+m-1 匹配结尾 

    printf("%d\n",ans[0]);
    for (int i=1;i<=ans[0];i++) printf("%d\n",ans[i]);
    return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值