2021航电多校3 - C Forgiving Matching (FTT)

题解

经典的卷积求字符串匹配问题,但是我看错题+太菜,没想到用FTT做。

先忽略通配符的影响,分字符计算出每个起点的最大匹配字符个数,卷积的时候让T串逆序即可。

考虑通配符的贡献,贡献为S中通配符的个数 + T中通配符的个数 - S和T中对应位置都是通配符的个数。前两项可以用前缀和计算,后一项可以用FTT计算。

注意FTT使用非递归写法,递归写法容易T。

#include <bits/stdc++.h>
#include <cmath>
#include <complex>

#define endl '\n'
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define mp make_pair
#define seteps(N) fixed << setprecision(N)
typedef long long ll;

using namespace std;
/*-----------------------------------------------------------------*/

ll gcd(ll a, ll b) {
    return b ? gcd(b, a % b) : a;
}
#define INF 0x3f3f3f3f
double PI = acos(-1);


typedef std::complex<double> Comp;  // STL complex

const Comp I(0, 1);  // i
const int N = 2e6;

Comp tmp[N];
Comp f1[N], f2[N];
int rev[N];


void change(Comp y[], int len) {
  for (int i = 0; i < len; ++i) {
    rev[i] = rev[i >> 1] >> 1;
    if (i & 1) {  // 如果最后一位是 1,则翻转成 len/2
      rev[i] |= len >> 1;
    }
  }
  for (int i = 0; i < len; ++i) {
    if (i < rev[i]) {  // 保证每对数只翻转一次
      swap(y[i], y[rev[i]]);
    }
  }
  return;
}

void fft(Comp y[], int len, int on) {
  change(y, len);
  for (int h = 2; h <= len; h <<= 1) {                  // 模拟合并过程
    Comp wn(cos(2 * PI / h), sin(on * 2 * PI / h));  // 计算当前单位复根
    for (int j = 0; j < len; j += h) {
      Comp w(1, 0);  // 计算当前单位复根
      for (int k = j; k < j + h / 2; k++) {
        Comp u = y[k];
        Comp t = w * y[k + h / 2];
        y[k] = u + t;  // 这就是把两部分分治的结果加起来
        y[k + h / 2] = u - t;
        // 后半个 “step” 中的ω一定和 “前半个” 中的成相反数
        // “红圈”上的点转一整圈“转回来”,转半圈正好转成相反数
        // 一个数相反数的平方与这个数自身的平方相等
        w = w * wn;
      }
    }
  }
  if (on == -1) {
    for (int i = 0; i < len; i++) {
      y[i] /= len;
    }
  }
}

int get(int x) {
    int res = 1;
    while(res < x) 
        res = (res << 1);
    return res;
}

char s1[N], s2[N];
int spre[N], ssum, scount[N];

void init(int len) {
    for(int i = 0; i <= len; i++) {
        f1[i] = f2[i] = Comp(0, 0);
    }
}

int ans[N];
int res[N];

int main() {
    IOS;
    int k;
    cin >> k;
    while(k--) {
        int n, m;
        cin >> n >> m;
        for(int i = 0; i < n; i++) ans[i] = res[i] = 0;
        int len = get(max(n, m));
        init(len);
        cin >> s1 >> s2;
        reverse(s2, s2 + m);
        
        ssum = 0;
        for(int i = 0; i < n; i++) {
            spre[i] = 0;
            if(s1[i] == '*') {
                spre[i] = 1;
                f1[i] = Comp(1, 0);
            }
            spre[i] += spre[i - 1];
        }
        for(int i = 0; i < m; i++) {
            if(s2[i] == '*') {
                ssum++;
                f2[i] = Comp(1, 0);
            }
        }

        fft(f1, len, 1);
        fft(f2, len, 1);
        for(int i = 0; i < len; i++) {
            f1[i] *= f2[i];
        }
        fft(f1, len, -1);
        for(int i = 0; i < len; i++) {
            scount[i] = (int)round(f1[i].real());
        }
        
        
        for(int i = 0; i < 10; i++) {
            init(len);
            for(int j = 0; j < n; j++) {
                if(s1[j] == i + '0') {
                    f1[j] = Comp(1, 0);
                }
            }
            for(int j = 0; j < m; j++) {
                if(s2[j] == i + '0') {
                    f2[j] = Comp(1, 0);
                }
            }
            fft(f1, len, 1);
            fft(f2, len, 1);
            for(int j = 0; j < len; j++) {
                f1[j] *= f2[j];
            }
            fft(f1, len, -1);
            for(int j = m - 1; j < n; j++) {
                ans[j] += (int)round(f1[j].real());
            }
        }
        
        for(int i = m - 1; i < n; i++) {
            ans[i] += (spre[i] + ssum - scount[i]);
            if(i - m >= 0) ans[i] -= spre[i - m];
            assert(m - ans[i] >= 0);
            res[m - ans[i]]++;
        }

        for(int i = 1; i <= m; i++) {
            res[i] += res[i - 1];
        }
        for(int i = 0; i <= m; i++) {
            cout << res[i] << endl;
        }
    }
}
posted @ 2021-08-02 11:15  limil  阅读(86)  评论(0编辑  收藏  举报