ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

关于FFT/NTT与字符串匹配

2021-01-31 17:04:04  阅读:252  来源: 互联网

标签:字符 匹配 loc int LL FFT NTT maxn 字符串


出于对字符串的恐惧,我决定小小的总结一下FFT/NTT在字符串匹配方面的使用。

NOTICE:这是我在做题目时的总结,内容并非全部原创,可能存在对相关题解的借鉴。

$对于含通配符的匹配问题

  • 描述:在某些题目中,我们可能会遇到可以匹配任何字符的通配符

通常,我们定义匹配函数 \(P(x)\):

\[P(x)=\sum_{i=0}^{m-1}{(a(i)-b(x-m+i+1))^2a(i)b(x-m+i+1)} \]

其中, \(m\) 为 \(a\) 串长度, \(a(i)\) 表示 \(a\) 串的第 \(i\) 项的字母对应的数值,其中特别定义通配符对应 \(0\)。

当 \(P(x)=0\) 时, \(b\) 串以 \(x\) 为结束位置的连续 \(m\) 位与 \(a\) 串匹配。

将 \(a\) 串反转并化简式子之后得到

\[P(x)=\sum_{i=0}^{m-1}{a^3(x-i)b(i)+b^3(i)a(x-i)-2a^2(x-i)b^2(i)} \]

发现此时可以用FFT优化计算。复杂度 \(O(n\log_2n)\)。

有了这个算法,P4173 残缺的字符串 就不是问题了

参考代码:

#include <cstdio>
#include <algorithm>
#include <cstring>
#define LL long long

using namespace std;

const int maxn = 2e6 + 10, P = 998244353, G = 3;
int n,m,rev[maxn],len;
int a1[maxn],a2[maxn],a3[maxn],b1[maxn],b2[maxn],b3[maxn],p[maxn];
int cnt,ans[maxn];

int Pow(LL x, LL y){
    LL ans = 1;
    while(y){
        if(y & 1) ans *= x, ans %= P;
        x *= x, x %= P;
        y >>= 1;
    } return ans;
}

void NTT(int *a, int opt){
    for(int i = 0; i < n; ++ i) if(i < rev[i]) swap(a[i], a[rev[i]]);
    for(int i = 1; i < n; i <<= 1){
        int gn = Pow(G, (P - 1) / (i << 1));
        if(opt < 0) gn = Pow(gn, P - 2);
        for(int j = 0; j < n; j += (i << 1)){
            int g = 1;
            for(int k = 0; k < i; ++ k, g = (LL)g * gn % P){
                int t1 = a[j + k], t2 = (LL)a[i + j + k] * g % P;
                a[j + k] = ((LL)t1 + t2) % P; a[i + j + k] = ((LL)t1 - t2 + P) % P;
            }
        }
    }
    if(opt < 0){
        int val = Pow(n, P - 2);
        for(int i = 0; i < n; ++ i) a[i] = (LL)a[i] * val % P;
    }
}

char s[maxn];
void get(int *a, int opt){
    scanf("%s", s + 1);
    int lens = strlen(s + 1);
    for(int i = 1; i <= lens; ++ i)
        if(s[i] != '*') a[i] = s[i] - 'a' + 1;
    if(opt) reverse(a + 1, a + 1 + lens);
}

int main(){
    scanf("%d%d", &n, &m); int N = n, M = m;
    get(a1, 1); get(b1, 0);
    for(int i = 1; i <= n; ++ i) a2[i] = (LL)a1[i] * a1[i] % P, a3[i] = (LL)a2[i] * a1[i] % P;
    for(int i = 1; i <= m; ++ i) b2[i] = (LL)b1[i] * b1[i] % P, b3[i] = (LL)b2[i] * b1[i] % P;
    m = n + m;
    for(n = 1; n <= m; n <<= 1) len++;
    for(int i = 0; i < n; ++ i) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (len - 1));
    NTT(a1, 1); NTT(a2, 1); NTT(a3, 1);
    NTT(b1, 1); NTT(b2, 1); NTT(b3, 1);
    for(int i = 0; i < n; ++ i) 
        p[i] += ((LL)a1[i] * b3[i] % P - (LL)a2[i] * b2[i] * 2 % P + (LL)a3[i] * b1[i] % P + P) % P, p[i] %= P;
    NTT(p, -1);
    for(int i = N + 1; i <= M + 1; ++ i){
        if(!p[i] && i - N <= M - N + 1) ans[++cnt] = i - N;
    }
    printf("%d\n", cnt);
    for(int i = 1; i <= cnt; ++ i) printf("%d ", ans[i]);
    printf("\n");
    return 0;
}

$对于移位匹配问题

  • 描述:有些题目可能允许在 \(k\) 个单位以内匹配,这一类题目一般会有一些其他的有用的性质。

我们可以以 CF528D Fuzzy Search 为例:

题意:给定原串 \(S\) 和匹配串 \(T\) (只由 \(A,C,G,T\) 组成),允许错开不超过 \(k\) 位匹配(即 \(T\) 中第 \(i\) 位字符可以与 \(S\) 中 \([i-k,i+k]\) 中的相同字符匹配)。问 \(T\) 在 \(S\) 中出现次数。\(|S|,|T|<200000\)

题中有一个重要性质:不同字符只有 \(4\) 个,这就是提示我们对于每一个字符分开处理。即对于每一个字符处理出成功匹配的数量,最后加在一起。

具体地,就是将匹配的字符设为 \(1\),其他设为 \(0\),反转再卷积即可。

那如何处理 \(k\) 的问题呢?

事实上,我们可以把 \(k\) 理解为 \(S\) 串中的字符可以向周围扩撒 \(k\) 位,换句话说,就是只要某一字符与离它最近的匹配字符距离小于等于 \(k\),该位置就可以是 \(1\)。而最近距离可以从前往后与从后自前扫两边得出。

这里给出处理的核心代码:

void deal(char *s, int len, char c, int *a){
    LL loc = -1e18;
    for(int i = 0; i < n; ++ i){
        if(s[i] == c) loc = i;
        if((LL)i - loc <= k) a[i] = 1;
    }
    loc = 1e18;
    for(int i = n - 1; i >= 0; -- i){
        if(s[i] == c) loc = i;
        if((LL)loc - i <= k) a[i] = 1;
    }
}

void solve(char c){
    memset(a, 0, sizeof(a)); memset(b, 0, sizeof(b));
    deal(s, n, c, a);
    for(int i = 0; i < m; ++ i) b[i] = (t[m - i - 1] == c);
    NTT(a, lim, 1); NTT(b, lim, 1);
    for(int i = 0; i < lim; ++ i) a[i] = (LL)a[i] * b[i] % P;
    NTT(a, lim, -1);
    for(int i = 0; i < lim; ++ i) ans[i] += a[i];
}

持续更新……

标签:字符,匹配,loc,int,LL,FFT,NTT,maxn,字符串
来源: https://www.cnblogs.com/whenc/p/14353040.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有