卷积与字符串问题

有些字符串匹配问题可以推出卷积公式,并通过FFT加速计算过程,下面通过几个简单的例子来了解一下。

卷积是什么

首先给出离散卷积的公式:
f ( x ) = i = g ( i ) h ( n i ) f(x) = \sum_{i=-\infty}^{\infty}g(i)h(n-i)
FFT虽说是求多项式相乘,但实质上就是对于所有可能取到的x求f(x)。怎么理解呢,比方说, x k {x^k} 这个多项式系数,无非就是求 x i x^i x j x^j 系数乘积之和,注意是i+j = k,这就是卷积。

FFT板子:

// ---------------------------- FFT -------------------------------/
typedef long double db;
const db PI = acos(-1.0);
struct Complex {
    db x, y;
    Complex(db x=0.0, db y=0.0) : x(x), y(y) {}
    Complex operator - (const Complex &b) const {
        return Complex(x-b.x, y-b.y);
    }
    Complex operator + (const Complex &b) const {
        return Complex(x+b.x, y+b.y);
    }
    Complex operator * (const Complex &b) const {
        return Complex(x*b.x-y*b.y, x*b.y+y*b.x);
    }
};
void change(Complex y[], int len) {
    for(int i = 1, j = len / 2; i < len - 1; i++) {
        if(i < j) swap(y[i], y[j]);
        int k = len / 2;;
        while(j >= k) {
            j -= k;
            k /= 2;
        }
        if(j < k) j+=k;
    }
}
void fft(Complex y[], int len, int on) {
    change(y, len);
    for(int h = 2; h <= len; h<<=1) {
        Complex wn(cos(-on*2*PI/h), sin(-on*2*PI/h));
    
        for(int j = 0; j < len; j+=h) {
            Complex w(1, 0);
            for(int k = j; k < j + h / 2; k++) {
                Complex u = y[k];
                Complex t = w*y[k+h/2];
                y[k] = u + t;
                y[k+h/2] = u - t;
                w = w*wn;
            }
        }
    }
    if(on == -1) {
        for(int i = 0; i < len; i++) {
            y[i].x /= len;
        }
    }
}
// ---------------------------- FFT ------------------------------- /

下面的代码不给出FFT板子的部分。

例题一

Rock Paper ScissorsGym - 101667H

题目大意

剪子(S)包袱§锤®的游戏,一个原串s,一个模式串p,模式串可以从前往后移,问模式串可以赢的最多次数是多少。

EXp:

第三组样例

12 4
RRRRRRRRRSSS
RRRS

模式串移到最后的位置

RRRRRRRRRSSS

​ RRRS

可以赢三次。

思路

可以先把模式串变换一下,P换成R,S换成P,R换成S,这样就是求最大匹配,就是匹配次数最多的问题。假如模式串长度为m,我们要计算原串[x-m+1: x]与模式串的匹配次数,可以先把模式串翻转(为了构成卷积的形式),然后分别计算PSR三种匹配情况,最后求和并找到最大的和。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
// ---------------------------- FFT ------------------------------- /
// ---------------------------- FFT ------------------------------- /

const int maxn = 6e5 + 10;
Complex x1[maxn], x2[maxn];
char s[maxn / 6], ss[maxn / 6];
int n, m, sum[maxn];

void update(char c) {
    int len = 1;
    while(len < 2*n  || len < 2*m ) len *= 2;
    for(int i = 0; i < n; i++) {
        x1[i] = Complex(s[i]==c, 0.0);
    }
    for(int i = n; i < len; i++) x1[i] = Complex(0.0, 0.0);
    for(int i = 0; i < m; i++) {
        x2[i] = Complex(ss[i]==c, 0.0);
    }
    for(int i = m; i < len; i++) x2[i] = Complex(0.0, 0.0);
    fft(x1, len, 1);
    fft(x2, len, 1);
    for(int i = 0; i < len; i++) {
        x1[i] = x1[i] * x2[i];
    }
    fft(x1, len, -1);
    len = n + m + 1;
    for(int i = 0; i < len; i++) {
        sum[i] += (int)(x1[i].x + 0.5);
    }
}

int main()
{
    // freopen("/Users/maoxiangsun/MyRepertory/acm/i.txt", "r", stdin);
    scanf("%d%d", &n, &m);
    scanf("%s%s", s, ss);
    for(int i = 0; i < m; i++) {
        if(ss[i] == 'S') ss[i] = 'P';
        else if(ss[i] == 'P') ss[i] = 'R';
        else if(ss[i] == 'R') ss[i] = 'S';
    }
    reverse(ss, ss + m);
    memset(sum, 0, sizeof(sum));
    update('P');
    update('R');
    update('S');
    int ans = 0;
    for(int i = m - 1; i < n + m + 1; i++) {
        ans = max(ans, sum[i]);
    }
    printf("%d\n", ans);
    return 0;
}
close

例题二

K-InversionsGym - 101002E

题目大意:

给出一个AB串S,长度为N

S[j] == ‘B’, S[j] == ‘A’, k == j - i ,k在[1, N-1]的每种的个数。

思路

看到 j - i == k是不是很容易想到卷积的形式,可以看成j+(-i)== k但是坐标不能是负的,简单,两边加上n-1那就成卷积f(k+n-1) =g(n-1-i)h(j) 的形式,然后找到需要输出的那个区间,闭眼输出就行。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
// ---------------------------- FFT ------------------------------- /
// ---------------------------- FFT ------------------------------- /
const int maxn = 9e6 + 100;
char s[maxn/4], p[maxn/2];
Complex x1[maxn], x2[maxn];
int n; 

int main()
{
    // freopen("/Users/maoxiangsun/MyRepertory/acm/i.txt", "r", stdin);
    scanf("%s", s);
    n = strlen(s);
    memset(p, 0, sizeof(p));
    for(int i = 0; i < n; i++) p[n-i-1] = s[i]; 
    int len0 = n*2, len = 1;
    while(len < len0 * 2) len<<=1;
    for(int i = 0; i < n; i++) {
        if(s[i] == 'B') x1[i] = Complex(1.0, 0);
        else x1[i] = Complex(0.0, 0.0);
    }
    for(int i = n; i < len; i++) {
        x1[i] = Complex(0.0, 0.0); 
    }
    for(int i = 0; i < n; i++) {
        if(p[i] == 'A') x2[i] = Complex(1.0, 0.0);
        else x2[i] = Complex(0.0, 0.0);
    }
    for(int i = n; i < len; i++) {
        x2[i] = Complex(0.0,0.0);
    }
    fft(x1, len, 1);fft(x2, len, 1);
    for(int i = 0; i < len; i++) {
        x1[i] = x1[i] * x2[i];
    }
    fft(x1, len, -1);
    for(int i = 0; i < n-1; i++) {
        printf("%d\n", (int)(x1[n-i-2].x + 0.5));
    }
    return 0;
}

例题三

含有通配符的匹配。

https://www.luogu.org/problemnew/solution/P4173 这篇文章写得不错,也是用到卷积。

C ( x , y ) = [ A ( x ) B ( y ) ] 2 A ( x ) B ( y ) C(x,y)=[A(x)−B(y)]^2A(x)B(y)

核心就是把上面那个式子暴力展开,把带通配符的标记为0,如果匹配的话卷积为0。

总结

在字符串匹配的任务中,用FFT往往可以搞定一些用KMP无法做到的任务,比方说字符串模糊匹配,字符串中带有卷积形式的问题求解,含有通配符匹配等。

猜你喜欢

转载自blog.csdn.net/sunmaoxiang/article/details/88586450
今日推荐