2021杭电多校第三场 1003——Forgiving Matching

本文介绍如何使用Fast Fourier Transform (FFT) 解决字符串匹配问题,通过预处理模式串和匹配串中的字符计数,以及特殊处理通配符,计算不同失配长度下的匹配字符串数量。实例展示了如何利用FFT优化计算模式串与匹配串的最大匹配长度。

题目大意

给你两个字符串,一个长度为 nnn 模式串,一个长度为 mmm 匹配串,由字符 000999 和 通配符 * 组成。问你匹配串在模式串中失配长度不超过 0,1,⋯ ,m0, 1,\cdots,m0,1,,m 的字符串一共有多少个。

解题思路

考虑采用 fftfftfft 来做。
预处理
对于模式串,我们开十个数组(桶),来标记 000999 出现的下标,如果出现了就标为 111,否则标为 000,对于通配符 ∗* ,将 000999 都标为 111,因为通配符是可以和任何字符匹配的,都能造成贡献。
对于匹配串,首先将匹配串反转,然后和模式串做同样的处理,唯一不同的就是通配符的处理。模式串中的通配符不能将所有位置都标记为 111,这样的话,如果模式串和匹配串都是通配符会重复计算,所以我们首先统计匹配串中通配符的个数,将通配符的所有位置都标为 000,最后统计答案的时候加上通配符的个数即可。
对于样例
012∗4012*40124
1∗31*313
模式串预处理的结果为

10010
01010
00110
00010
00011
00000
00000
00000
00000
00000

fftfftfft 快速处理
定义模式串中的第 iii 个串为,以第 iii 个字符开头,长度为 mmm 的连续子串。(iii000 开始)
用模式串中的 000 数组与匹配串中的 000 数组做 fftfftfft 可以得到 fftfftfft 之后的数组 aaa。其中 am−i−1a_{m-i-1}ami1 即为模式串的第 iii 个串与匹配串中 000 匹配的个数。
我们对 000999 每个数组做一次 fftfftfft ,然后对每一次求和一下 am−i−1a_{m-i-1}ami1,再加上匹配串中通配符的个数,就可得到第 iii 个模式串和匹配串最大匹配长度。

求答案
最后我们统计一个失配为 000 到失配为 mmm 的前缀和即可。

反转操作很重要
比如你想知道 a1,a2,a3a_1, a_2, a_3a1,a2,a3b1,b2,b3b_1,b_2,b_3b1,b2,b3有多少个是匹配的,你不反转第二个字符串,你的结果会存在 c2,c4,c6c_2, c_4, c_6c2,c4,c6 之中,因为 c2=a1×b1,c4=a2×b2,c6=a3×b3c_2 = a_1\times b_1, c_4=a_2\times b_2, c_6 = a_3\times b_3c2=a1×b1,c4=a2×b2,c6=a3×b3,但是反转之后结果就直接存在 c4c_4c4 之中, 因为 c4=a1×b3+a2×b2+a3×b1c_4 = a_1\times b_3 + a_2\times b_2 + a_3\times b_1c4=a1×b3+a2×b2+a3×b1,这样才是我们 fftfftfft 的意义所在。

Code

#include <bits/stdc++.h>
#define ll long long
#define qc ios::sync_with_stdio(false); cin.tie(0);cout.tie(0)
#define fi first
#define se second
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define pb push_back
using namespace std;
const int MAXN = 1e6 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 1e9 + 7;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while (!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
    while (isdigit(ch)){x=x*10+ch-48;ch=getchar();}
    return x*f;
}
char s[MAXN],t[MAXN];
int f[10][MAXN];
int g[10][MAXN];
int n, m;
const double PI = acos(-1.0);
struct Complex
{
    double x, y;
    Complex operator+(const Complex &W) const
    {
        return {x + W.x, y + W.y};
    }
    Complex operator-(const Complex &W) const
    {
        return {x - W.x, y - W.y};
    }
    Complex operator*(const Complex &W) const
    {
        return {x * W.x - y * W.y, x * W.y + y * W.x};
    }
};
Complex a[MAXN], b[MAXN];
int R[MAXN];
int tot, bit;
void inif(int n)
{
    tot = 1, bit = 0;
    while (tot <= n)
        tot <<= 1, ++bit;
    for (int i = 0; i <= tot; ++i)
        R[i] = (R[i >> 1] >> 1) | ((i & 1) << (bit - 1));
}
void FFT(Complex f[], int total, int type, int n, int m)
{
    for (int i = 0; i < total; ++i)
        if (i < R[i])
            swap(f[i], f[R[i]]);
    for (int tot = 2; tot <= total; tot <<= 1)
    {
        Complex w1 = {cos(2 * PI / tot), type * sin(2 * PI / tot)};
        for (int pos = 0; pos < total; pos += tot)
        {
            Complex w = {1, 0};
            for (int i = pos; i < pos + tot / 2; ++i, w = w * w1)
            {
                Complex x = f[i];
                Complex y = w * f[i + tot / 2];
                f[i] = x + y;
                f[i + tot / 2] = x - y;
            }
        }
    }
    if (type == -1)
    {
        for (int i = 0; i <= n + m; ++i)
            f[i].x = (int)(f[i].x / tot + 0.5);
    }
}

// 用法
int tong[MAXN];
int ans[MAXN];
int pre[MAXN];
void solve(){
    cin >> n >> m;
	cin >> (s) >> (t);
	for (int i = 0; i < n; ++i){
	    if(s[i] == '*'){
			for (int j = 0; j <= 9; ++j){
			    f[j][i] = 1;
			}
		}
		else
			f[s[i]-'0'][i] = 1;
	}
	int cnt = 0;
	reverse(t, t+m);
	for (int i = 0; i < m; ++i){
	    if(t[i] == '*')
			cnt++;
		else
			g[t[i]-'0'][i] = 1;
	}
	for(int i = 0; i <= 9; ++i){
		for (int j = 0; j < n; ++j){
		    a[j].x = f[i][j];
		}
		for (int j = 0; j < m; ++j){
		    b[j].x = g[i][j];
		}
		inif(n + m);
		FFT(a, tot, 1, n, m), FFT(b, tot, 1, n, m);
		for (int j = 0; j <= tot-1; ++j){
		    a[j] = a[j] * b[j];
		}
		FFT(a, tot, -1, n, m);
		// 开始处理
		for (int j = m-1; j <= n-1; ++j){
		    tong[j] += (int)a[j].x;
		}
		for (int j = 0; j <= tot; ++j){
		    a[j].x = a[j].y = b[j].x = b[j].y = 0;
		}
	}
	for (int i = m-1; i <= n-1; ++i){
	    ans[tong[i] + cnt]++;
	}
	pre[m+1] = 0;
	for(int i = m; i >= 0; --i){
		pre[i] = pre[i+1] + ans[i];
	}
	for(int i = m; i >= 0; i--)
		cout << pre[i] << "\n";
	for(int i = m-1; i <= n-1; i++) tong[i] = 0;
	for (int i = 0; i <= m; ++i){
	    ans[i] = 0;
	}
	for (int i = 0; i <= 9; ++i){
	    for (int j = 0; j <= n; ++j){
	        f[i][j] = g[i][j] = 0;
	    }
	}
}

signed main()
{
    #ifdef ONLINE_JUDGE
    #else
       freopen("in.txt", "r", stdin);
       freopen("out.txt", "w", stdout);
    #endif

    qc;
    int T;
    cin >> T;
    //T = 1;
    while(T--){

        solve();
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值