HDU6975 Forgiving Matching

链接传送门


这题比赛上做出来的人挺多的,于是我就一直在想SAM,结果这是一道用FFT解决的字符串模糊匹配的板子题。


话说我很久以前还写过luogu P4173 残缺的字符串这道题,和比赛的题很像。

首先能想到的是,对于长度为(m)的每个(S)的子串,我们只要统计其与(T)匹配的位置数(f_i),即可以得到该子串被认为匹配的最小的(k)值。

对于求(f_i):先不考虑通配符的影响,因为字符集只有'0'~'9',所以我们可以每个字符单独考虑。拿'0'为例,将(S,T)所有是'0'的地方置为(1),其他为(0),则$$f(x)=sumlimits_{i=0}^{m-1}T(i)*S(x-m+1+i)$$

按照套路,将(T)反转,即(T(i)=T'(m - i - 1)),则$$f(x)=sumlimits_{i=0}^{m-1}T'(m - i - 1) * S(x - m + i + 1)$$会发现等式右边括号内的和刚好是(x),变量代换得$$f(x) = sumlimits_{j=0}^{m - 1}T'(j) * S(x-j)$$这几乎就是卷积的形式了,只不过卷积的上界是(x),但到(x)和到(m-1)实际上是没有区别的,因此用对'0'~'9'依次用一遍FFT,就能求出(f(i))了。


现在考虑通配字符,(S)的一个子串通过通配字符与(T)匹配的位置数=该子串的通配字符数+(T)中通配字符数-该子串和(T)中同一位置都是通配字符的数量。该子串的通配字符数用前缀和求得,该淄川和(T)中同一位置都是通配字符的数量同样同FFT可以求得。

#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<vector>
#include<queue>
#include<assert.h>
#include<ctime>
using namespace std;
#define enter puts("") 
#define space putchar(' ')
#define Mem(a, x) memset(a, x, sizeof(a))
#define In inline
#define forE(i, x, y) for(int i = head[x], y; ~i && (y = e[i].to); i = e[i].nxt)
typedef long long ll;
typedef double db;
const int INF = 0x3f3f3f3f;
const db eps = 1e-8;
const int maxn = 5.5e5 + 5;
const db PI = acos(-1);
In ll read()
{
	ll ans = 0;
	char ch = getchar(), las = ' ';
	while(!isdigit(ch)) las = ch, ch = getchar();
	while(isdigit(ch)) ans = (ans << 1) + (ans << 3) + ch - '0', ch = getchar();
	if(las == '-') ans = -ans;
	return ans;
}
In void write(ll x)
{
	if(x < 0) x = -x, putchar('-');
	if(x >= 10) write(x / 10);
	putchar(x % 10 + '0');
}
In void MYFILE()
{
#ifndef mrclr
	freopen(".in", "r", stdin);
	freopen(".out", "w", stdout);
#endif
}

int n, m;
char s[maxn], t[maxn];

int len = 1, lim = 0, rev[maxn];
struct Comp
{
	db x, y;
	In Comp operator + (const Comp& oth)const {return (Comp){x + oth.x, y + oth.y};}
	In Comp operator - (const Comp& oth)const {return (Comp){x - oth.x, y - oth.y};}
	In Comp operator * (const Comp& oth)const {return (Comp){x * oth.x - y * oth.y, x * oth.y + y * oth.x};}
	friend In void swap(Comp &a, Comp& b) {swap(a.x, b.x), swap(a.y, b.y);}
}A[maxn], B[maxn];
In void fft(Comp* a, int flg)
{
	for(int i = 0; i < len; ++i) if(i < rev[i]) swap(a[i], a[rev[i]]);
	for(int i = 1; i < len; i <<= 1)
	{
		Comp omg = (Comp){cos(PI / i), sin(PI / i) * flg};
		for(int j = 0; j < len; j += (i << 1))
		{
			Comp o = (Comp){1, 0};
			for(int k = 0; k < i; ++k, o = o * omg)
			{
				Comp tp1 = a[k + j], tp2 = o * a[k + j + i];
				a[k + j] = tp1 + tp2, a[k + j + i] = tp1 - tp2;
			}
		}
	}
}

int f[maxn], g[maxn], f1[maxn], ans[maxn];	
//f[i]:即题解中的f[i] 
//g[i]:以i结尾的子串和T同一位置都是通配字符的数量 
//f1[i]:S中通配字符数量的前缀和 
In void solve()
{
	reverse(t, t + m);
	len = 1, lim = 0;
	while(len <= n + m) len <<= 1, ++lim;
	fill(f, f + n, 0);
	for(int i = 0; i < len; ++i) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (lim - 1));
	for(int x = 0; x <= 10; ++x)
	{
		for(int i = 0; i < len; ++i) A[i] = B[i] = (Comp){0, 0};
		char c = x == 10 ? '*' : x + '0';
		for(int i = 0; i < n; ++i) if(s[i] == c) A[i].x = 1;
		for(int i = 0; i < m; ++i) if(t[i] == c) B[i].x = 1;
		fft(A, 1), fft(B, 1);
		for(int i = 0; i < len; ++i) A[i] = A[i] * B[i];
		fft(A, -1);
		for(int i = 0; i < n; ++i) 
			if(x == 10) g[i] = A[i].x / len + 0.5;
			else f[i] += A[i].x / len + 0.5;
	}
	int f2 = 0;
	for(int i = 0; i < n; ++i) f1[i] = (i ? f1[i - 1] : 0) + (s[i] == '*');
	for(int i = 0; i < m; ++i) f2 += (t[i] == '*');
	fill(ans, ans + m + 1, 0);
	for(int i = m - 1; i < n; ++i) ans[m - (f[i] + f1[i] - (i < m ? 0 : f1[i - m]) + f2 - g[i])]++;
	for(int i = 0; i <= m; ++i)
	{
		if(i) ans[i] += ans[i - 1];
		write(ans[i]), enter;
	}
}

int main()
{
	int T = read();
	while(T--)
	{
		n = read(), m = read();
		scanf("%s%s", s, t);
		solve();
	}
	return 0;
}
原文地址:https://www.cnblogs.com/mrclr/p/15071763.html