牛客网暑期ACM多校训练营(第三场)D 字符串匹配 FFT

题目链接


题意:给定两个串A,B,问A中有多少个长度等于B的子串与B相似,两个同样长度的字符串相似的条件是:对于对应位置上的字符,差的绝对值不大于1


思路:
可以类似于FFT用于字符串匹配的算法。
假设 A A A的长度为 m m m B B B的长度为 n n n,且下标均从1开始
则相似子串的个数:

A n s = ∑ j = 0 m − 1 [ ∑ i = 1 n ( A i + j − B i ) 2 ∗ ( ( A i + j − B i ) 2 − 1 ) = 0 ] Ans = \sum_{j=0}^{m-1}[\sum_{i=1}^{n}(A_{i+j}-B_i)^2*((A_{i+j}-B_i)^2 - 1) = 0] Ans=j=0m1[i=1n(Ai+jBi)2((Ai+jBi)21)=0]

将中间的式子展开:
( A i + j − B i ) 2 ∗ ( ( A i + j − B i ) 2 − 1 ) (A_{i+j}-B_i)^2*((A_{i+j}-B_i)^2 - 1) (Ai+jBi)2((Ai+jBi)21)

= ( A i + j 2 + B i 2 − 2 A i + j B i ) ∗ ( A i + j 2 + B i 2 − 2 A i + j B i − 1 ) =(A_{i+j}^2+B_i^2-2A_{i+j}B_i)*(A_{i+j}^2+B_i^2-2A_{i+j}B_i - 1) =(Ai+j2+Bi22Ai+jBi)(Ai+j2+Bi22Ai+jBi1)
= ( A i + j 4 − A i + j 2 ) + ( B i 4 − B i 2 ) + 6 A i + j 2 B i 2 + 2 A i + j B i − 4 A i + j 3 B i − 4 A i + j B i 3 =(A_{i+j}^4-A_{i+j}^2)+(B_i^4-B_i^2)+6A_{i+j}^2B_i^2+2A_{i+j}B_i-4A_{i+j}^3B_i-4A_{i+j}B_i^3 =(Ai+j4Ai+j2)+(Bi4Bi2)+6Ai+j2Bi2+2Ai+jBi4Ai+j3Bi4Ai+jBi3

前面的四项可以处理一个前缀和,而后面的四项可以通过反转B以后进行卷积,FFT优化。

随后就可以枚举每个位置,O(1)判断了

以上。


代码:

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;

const double pi = acos(-1.0);
const int A = 250000 + 10;
const int B = A<<3;
char S[A], T[A], E[30];
int n, m, tot, len, Ans[A];
ll sumx[A], sumy[A];

struct comp{
    double r, i; comp(double _r = 0, double _i = 0) {r = _r; i = _i;}
    comp operator + (const comp x){return comp(r+x.r, i+x.i);}
    comp operator - (const comp x){return comp(r-x.r, i-x.i);}
    comp operator * (const comp x){return comp(r*x.r-i*x.i, r*x.i+i*x.r);}
    comp operator * (const int x){return comp(r*x, i*x);}
}X[4][B], Y[3][B], Z[B];

void FFT(comp a[], int n, int t){
    for (int i = 1, j = 0; i < n - 1; i++) {
        for (int s = n; j ^= s>>=1, ~j&s;);
        if (i < j) swap(a[i], a[j]);
    }
    for (int d = 0; (1<<d) < n; d++) {
        int m = 1<<d, m2 = m<<1;
        double o = pi/m*t; comp _w(cos(o), sin(o));
        for (int i = 0; i < n; i += m2) {
            comp w(1, 0);
            for (int j = 0; j < m; j++) {
                comp &A = a[i+j+m], &B = a[i+j], t = w*A;
                A = B - t; B = B + t; w = w * _w;
            }
        }
    }
    if (t == -1) for(int i = 0; i < n; i++) a[i].r /= n;
}

void Init(){
    len = 1;
    while (len < m+2) len <<= 1;
    sumx[0] = sumy[0] = 0;
    for (int i = 1; i <= n; i++) {
        int x = S[i] - 'a' + 1;
        sumx[i] = sumx[i-1] + (x*x*x*x - x*x);
        X[0][n-i+1] = comp(2.0*x, 0);
    	X[1][n-i+1] = comp(6.0*x*x, 0);
    	X[2][n-i+1] = comp(4.0*x*x*x, 0);
    	X[3][n-i+1] = comp(4.0*x, 0);
    }
    for (int i = 1; i <= m; i++) {
        int y = T[i] - 'a' + 1;
        sumy[i] = sumy[i-1] + (y*y*y*y - y*y);
        Y[0][i] = comp(1.0*y, 0);
    	Y[1][i] = comp(1.0*y*y, 0);
    	Y[2][i] = comp(1.0*y*y*y, 0);
    }
}

void calc(){
    Init();
    for (int i = 0; i < 3; i++) {
        FFT(X[i], len, 1);
        FFT(Y[i], len, 1);
    }
    FFT(X[3], len, 1);
    for (int i = 0; i < len; i++) Z[i] = X[1][i]*Y[1][i] + X[0][i]*Y[0][i] - X[2][i]*Y[0][i] - X[3][i]*Y[2][i];
    FFT(Z, len, -1);

    tot = 0;
    for (int j = 0; j <= m - n; j++) {
        if ((ll)(Z[n+j+1].r + sumx[n] + sumy[n+j] - sumy[j]) == 0) Ans[++tot] = j + 1;
    }
    printf("%d\n", tot);
    for (int i = 1; i <= tot; i++) printf("%d%c", Ans[i], i==tot?'\n':' ');
}

int main(){
    scanf("%s%s%s", S + 1, T + 1, E);
    n = strlen(S + 1);
    m = strlen(T + 1);
    for (int i = 1; i <= n; i++) S[i] = E[S[i]-'a'];
    for (int i = 1; i <= m; i++) T[i] = E[T[i]-'a'];
    calc();
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值