带通配符的字符串匹配
在 PKUSC2018 中遇到了这样一道模板题,但是没有做出来,把这类型的题找了找,发现其实有两种不同的方法。
首先是 PKUSC 上的题。
[LOJ#6436][BZOJ5372]「PKUSC2018」神仙的游戏
试题描述
小 (D) 和小 (H) 是两位神仙。他们经常在一起玩神仙才会玩的一些游戏,比如 “口算一个 (4) 位数是不是完全平方数” 。
今天他们发现了一种新的游戏:首先称 (s) 长度为 (len) 的前缀成为 border 当且仅当 (s[1...len] = s[|s| - len + 1 ... |s|])。给出一个由 01? 组成的字符串 (s), 将 (s) 中的问号用变成 01 替换,对每个 (len) 口算是否存在替换问号的方案使得 (s) 长度为 (len) 的前缀成为 border,把这个结果记做 (f(len)in {0,1})。(f(len) = 1) 如果 (s) 长度为 (len) 的前缀能够成为 border,否则 (f(len) = 0)。
由于小 (D) 和小 (H) 是神仙,所以他们计算的 (s) 的长度很长,因此把计算的结果一一比对会花费很长的时间。为了方便比对,他们规定了一个校验值:((f(1) imes 1^2) xor (f(2) imes 2^2) xor (f(3) imes 3^2) xor cdots xor (f(n) imes n^2))
来校验他们的答案是否相同。(xor) 表示按位异或。但是不巧,在某一次游戏中,他们口算出的校验值并不一样,他们希望你帮助他们来计算一个正确的校验值。当然,他们不强迫你口算,可以编程解决。
输入
一个串 (s), 保证每个字符都是 0,1,或者?.
输出
输出字符串的校验值, 即 ((f(1) imes 1^2) xor (f(2) imes 2^2) xor (f(3) imes 3^2) xor cdots xor (f(n) imes n^2))。
输入示例
1?0?
输出示例
17
数据规模及约定
(|s| le 5 imes 10^5)
题解
若串 (s) 有一个长度为 (l) 的 border,则它就有一个 (|s| - l) 的周期(定义周期 (x) 表示 (forall i in [1, n - x], s_i = s_{i+x})),这道题我们判断它有哪些可行的周期,就能找到它所有的 border。
在已确定的字符中,两个不同的字符显然是不能属于一个周期的同一个位置的,形式化地解释一下:假定 (s_x = 1, s_y = 0),那么 (|x - y|) 就不可能是周期。
可以证明,排除上面所有上面方法找到的不合法的周期以及所有它们的约数之后,剩下的都是合法的周期。
合法的周期 (l) 需要满足 (forall t < l, k in left[ 0, left lfloor frac{n}{l} ight floor ight]),所有的 (s_{t+kl}) 都能变成同一个字符,也就是以任意位置为起点,步长为 (l) 在这个串上跳,全程不能同时遇到 (0) 和 (1)。所以显然排除掉上面所找到的不合法周期后,剩下的就都是合法周期。
接下来就是找到所有出现的 (x - y),我们开两个多项式 (A(z)) 和 (B(z)),当 (s_x = 1) 时 ([z^x]A(z) = 1),当 (s_x = 0) 是 ([z^{n-x}]B(z) = 1),其余的都为 (0);那么若 ([z^a] A(z) B(z) e 0),则说明 (a - n) 就是出现过的 (x - y)。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 1048576
#define LL long long
namespace FFT {
const double pi = acos(-1.0);
struct Complex {
double a, b;
Complex(double _ = 0, double __ = 0): a(_), b(__) {}
Complex operator + (const Complex &t) const { return Complex(a + t.a, b + t.b); }
Complex operator - (const Complex &t) const { return Complex(a - t.a, b - t.b); }
Complex operator * (const Complex &t) const { return Complex(a * t.a - b * t.b, a * t.b + b * t.a); }
Complex operator *= (const Complex &t) { return *this = *this * t; }
} a[maxn], b[maxn];
int brev[maxn];
void FFT(Complex *a, int len, int tp) {
int n = 1 << len;
rep(i, 0, n - 1) if(brev[i] < i) swap(a[i], a[brev[i]]);
rep(i, 1, len) {
Complex wn(cos(2.0 * pi / (1 << i)), tp * sin(2.0 * pi / (1 << i)));
for(int j = 0; j < n; j += 1 << i) {
Complex w(1);
rep(k, 0, (1 << i >> 1) - 1) {
Complex la = a[j+k], ra = a[j+k+(1<<i>>1)] * w;
a[j+k] = la + ra;
a[j+k+(1<<i>>1)] = la - ra;
w *= wn;
}
}
}
if(tp < 0) rep(i, 0, n - 1) a[i].a /= n;
return ;
}
void Mul(int *A, int *B, int n, int m) {
int N = 1, len = 0;
while(N <= n + m) N <<= 1, len++;
rep(i, 0, N - 1) brev[i] = (brev[i>>1] >> 1) | ((i & 1) << len >> 1);
rep(i, 0, n) a[i] = Complex(A[i]); rep(i, n + 1, N - 1) a[i] = Complex();
rep(i, 0, m) b[i] = Complex(B[i]); rep(i, m + 1, N - 1) b[i] = Complex();
FFT(a, len, 1); FFT(b, len, 1);
rep(i, 0, N - 1) a[i] *= b[i];
FFT(a, len, -1);
rep(i, 0, N - 1) A[i] = (int)(a[i].a + .5);
return ;
}
}
char str[maxn];
int n, A[maxn], B[maxn];
bool has[maxn];
int main() {
scanf("%s", str + 1);
n = strlen(str + 1);
rep(i, 1, n)
if(str[i] == '0') A[i] = 1;
else if(str[i] == '1') B[n-i] = 1;
FFT::Mul(A, B, n, n);
rep(i, 0, n << 1) if(A[i]) has[abs(i-n)] = 1;
rep(i, 1, n) {
bool h = 0;
for(int j = i; j <= n; j += i) if(has[j]) { h = 1; break; }
has[i] = h;
}
LL ans = 0;
rep(i, 0, n) if(!has[i]) ans ^= (LL)(n - i) * (n - i);
printf("%lld
", ans);
return 0;
}
[BZOJ4503]两个串
试题描述
兔子们在玩两个串的游戏。给定两个字符串 (S) 和 (T),兔子们想知道 (T) 在 (S) 中出现了几次,分别在哪些位置出现。注意 (T) 中可能有“?”字符,这个字符可以匹配任何字符。
输入
两行两个字符串,分别代表 (S) 和 (T)
输出
第一行一个正整数 (k),表示 (T) 在 (S) 中出现了几次
接下来 (k) 行正整数,分别代表 (T) 每次在 (S) 中出现的开始位置。按照从小到大的顺序输出,(S) 下标从 (0) 开始。
输入示例
bbabaababaaaaabaaaaaaaabaaabbbabaaabbabaabbbbabbbbbbabbaabbbababababbbbbbaaabaaabbbbbaabbbaabbbbabab
a?aba?abba
输出示例
0
数据规模及约定
(S) 长度不超过 (10^5),(T) 长度不会超过 (S)。(S) 中只包含小写字母, (T) 中只包含小写字母和“?”
题解
这题可以沿用上一题的做法:我们发现所有已确定的不同的字母是不可能匹配的,比如 (S_x = a, T_y = b),那么位置 (x - y) 不可能成为答案,所以照上面那题的方法对于每个字符都做一遍就好了。
但是 (26) 倍大常数带你 T 飞。
于是这里就需要换一种方法。我们用一种更加数学的方式描述匹配:位置 (k) 能够匹配 (iff sum_{i=0}^{m-1} (a_{k+i} - b_{m-i-1})^2 cdot b_{m-i-1} = 0)(这里定义 (a_i = S_i, b_i = T_{m-i-1}),特别地当 (T_{m-i-1} = ?) 时 (b_i = 0))。
我们把平方拆开就得到 (sum_{i=0}^{m-1} a_{k+i}^2 cdot b_{m-i-1} - 2 sum_{i=0}^{m-1} a_{k+i} cdot b_{m-i-1}^2 + sum_{i=0}^{m-1} b_{m-i-1}^3),这又是几个卷积的形式,每个分别做一遍 FFT 即可。
#include <bits/stdc++.h>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 262144
namespace FFT {
const double pi = acos(-1.0);
struct Complex {
double a, b;
Complex(double _ = 0, double __ = 0): a(_), b(__) {}
Complex operator + (const Complex &t) const { return Complex(a + t.a, b + t.b); }
Complex operator - (const Complex &t) const { return Complex(a - t.a, b - t.b); }
Complex operator * (const Complex &t) const { return Complex(a * t.a - b * t.b, a * t.b + b * t.a); }
Complex operator *= (const Complex &t) { return *this = *this * t; }
} a[maxn], b[maxn];
int brev[maxn];
void FFT(Complex *a, int len, int tp) {
int n = 1 << len;
rep(i, 0, n - 1) if(i < brev[i]) swap(a[i], a[brev[i]]);
rep(i, 1, len) {
Complex wn(cos(2.0 * pi / (1 << i)), tp * sin(2.0 * pi / (1 << i)));
for(int j = 0; j < n; j += 1 << i) {
Complex w(1);
rep(k, 0, (1 << i >> 1) - 1) {
Complex la = a[j+k], ra = w * a[j+k+(1<<i>>1)];
a[j+k] = la + ra;
a[j+k+(1<<i>>1)] = la - ra;
w *= wn;
}
}
}
if(tp < 0) rep(i, 0, n - 1) a[i].a /= n;
return ;
}
void Mul(int *A, int *B, int n, int m) {
int N = 1, len = 0;
while(N <= n + m) N <<= 1, len++;
rep(i, 0, N - 1) brev[i] = (brev[i>>1] >> 1) | ((i & 1) << len >> 1);
rep(i, 0, n) a[i] = Complex(A[i]); rep(i, n + 1, N - 1) a[i] = Complex();
rep(i, 0, m) b[i] = Complex(B[i]); rep(i, m + 1, N - 1) b[i] = Complex();
FFT(a, len, 1); FFT(b, len, 1);
rep(i, 0, N - 1) a[i] *= b[i];
FFT(a, len, -1);
rep(i, 0, n + m) A[i] = (int)(a[i].a + .5);
return ;
}
}
namespace String {
char S[maxn], T[maxn];
int n, m, sa[maxn], tb[maxn], A[maxn], B[maxn], F[maxn], can[maxn], cnt;
void process() {
rep(i, 0, n) sa[i] = S[i] - 'a' + 1;
rep(i, 0, m) tb[i] = isalpha(T[m-i]) ? T[m-i] - 'a' + 1 : 0;
rep(i, 0, n) A[i] = sa[i] * sa[i];
rep(i, 0, m) B[i] = tb[i];
FFT::Mul(A, B, n, m);
rep(i, 0, n + m) F[i] = A[i];
rep(i, 0, n) A[i] = sa[i];
rep(i, 0, m) B[i] = tb[i] * tb[i];
FFT::Mul(A, B, n, m);
rep(i, 0, n + m) F[i] -= A[i] << 1;
rep(i, 0, n) A[i] = 1;
rep(i, 0, m) B[i] = tb[i] * tb[i] * tb[i];
FFT::Mul(A, B, n, m);
rep(i, 0, n + m) F[i] += A[i];
rep(i, m, n) if(!F[i]) can[++cnt] = i - m;
return ;
}
void main() {
scanf("%s%s", S, T);
n = strlen(S) - 1; m = strlen(T) - 1;
process();
printf("%d
", cnt);
rep(i, 1, cnt) printf("%d%c", can[i], i < cnt ? ' ' : '
');
return ;
}
}
int main() {
String::main();
return 0;
}
[BZOJ4259]残缺的字符串
试题描述
很久很久以前,在你刚刚学习字符串匹配的时候,有两个仅包含小写字母的字符串 (A) 和 (B),其中 (A) 串长度为 (m),(B) 串长度为 (n)。可当你现在再次碰到这两个串时,这两个串已经老化了,每个串都有不同程度的残缺。
你想对这两个串重新进行匹配,其中 (A) 为模板串,那么现在问题来了,请回答,对于 (B) 的每一个位置 (i),从这个位置开始连续 (m) 个字符形成的子串是否可能与 (A) 串完全匹配?
输入
第一行包含两个正整数 (m,n(1 le m le n le 300000)),分别表示 (A) 串和 (B) 串的长度。
第二行为一个长度为 (m) 的字符串 (A)。
第三行为一个长度为 (n) 的字符串 (B)。
两个串均仅由小写字母和号组成,其中号表示相应位置已经残缺。
输出
第一行包含一个整数 (k),表示 (B) 串中可以完全匹配 (A) 串的位置个数。
若 (k>0),则第二行输出 (k) 个正整数,从小到大依次输出每个可以匹配的开头位置(下标从 (1) 开始)。
输入示例
3 7
a*b
aebr*ob
输出示例
2
1 5
数据规模及约定
见“输入”
题解
和上面的题做法基本相同,我们将“位置 (k) 能够匹配”的充分必要条件中的公式改成 (sum_{i=k}^{m-1} (a_{i+k} - b_{m-i-1})^2 cdot a_{i+k} cdot b_{m-i-1} = 0) 即可。
注意这题需要开 long long。我们把公式换成 (sum_{i=k}^{m-1} (a_{i+k} - b_{m-i-1})^2 cdot [a_{i+k}
e 0] cdot [b_{m-i-1}
e 0] = 0) 就不用开 long long 啦!(然而并没有快多少)
#include <bits/stdc++.h>
using namespace std;
#define rep(i, s, t) for(int i = (s), mi = (t); i <= mi; i++)
#define dwn(i, s, t) for(int i = (s), mi = (t); i >= mi; i--)
int read() {
int x = 0, f = 1; char c = getchar();
while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
#define maxn 1048576
namespace FFT {
const double pi = acos(-1.0);
struct Complex {
double a, b;
Complex(double _ = 0, double __ = 0): a(_), b(__) {}
Complex operator + (const Complex &t) const { return Complex(a + t.a, b + t.b); }
Complex operator - (const Complex &t) const { return Complex(a - t.a, b - t.b); }
Complex operator * (const Complex &t) const { return Complex(a * t.a - b * t.b, a * t.b + b * t.a); }
Complex operator *= (const Complex &t) { return *this = *this * t; }
} a[maxn], b[maxn];
int brev[maxn];
void FFT(Complex *a, int len, int tp) {
int n = 1 << len;
rep(i, 0, n - 1) if(i < brev[i]) swap(a[i], a[brev[i]]);
rep(i, 1, len) {
Complex wn(cos(2.0 * pi / (1 << i)), tp * sin(2.0 * pi / (1 << i)));
for(int j = 0; j < n; j += 1 << i) {
Complex w(1);
rep(k, 0, (1 << i >> 1) - 1) {
Complex la = a[j+k], ra = w * a[j+k+(1<<i>>1)];
a[j+k] = la + ra;
a[j+k+(1<<i>>1)] = la - ra;
w *= wn;
}
}
}
if(tp < 0) rep(i, 0, n - 1) a[i].a /= n;
return ;
}
void Mul(int *A, int *B, int n, int m) {
int N = 1, len = 0;
while(N <= n + m) N <<= 1, len++;
rep(i, 0, N - 1) brev[i] = (brev[i>>1] >> 1) | ((i & 1) << len >> 1);
rep(i, 0, n) a[i] = Complex(A[i]); rep(i, n + 1, N - 1) a[i] = Complex();
rep(i, 0, m) b[i] = Complex(B[i]); rep(i, m + 1, N - 1) b[i] = Complex();
FFT(a, len, 1); FFT(b, len, 1);
rep(i, 0, N - 1) a[i] *= b[i];
FFT(a, len, -1);
rep(i, 0, n + m) A[i] = (int)(a[i].a + .5);
return ;
}
}
namespace String {
char S[maxn], T[maxn];
int n, m, sa[maxn], tb[maxn], A[maxn], B[maxn], F[maxn], can[maxn], cnt;
void process() {
rep(i, 0, n) sa[i] = isalpha(S[i]) ? S[i] - 'a' + 1 : 0;
rep(i, 0, m) tb[i] = isalpha(T[m-i]) ? T[m-i] - 'a' + 1 : 0;
rep(i, 0, n) A[i] = sa[i] * sa[i] * (sa[i] != 0);
rep(i, 0, m) B[i] = tb[i] != 0;
FFT::Mul(A, B, n, m);
rep(i, 0, n + m) F[i] = A[i];
rep(i, 0, n) A[i] = sa[i] * (sa[i] != 0);
rep(i, 0, m) B[i] = tb[i] * (tb[i] != 0);
FFT::Mul(A, B, n, m);
rep(i, 0, n + m) F[i] -= A[i] << 1;
rep(i, 0, n) A[i] = sa[i] != 0;
rep(i, 0, m) B[i] = tb[i] * tb[i] * (tb[i] != 0);
FFT::Mul(A, B, n, m);
rep(i, 0, n + m) F[i] += A[i];
rep(i, m, n) if(!F[i]) can[++cnt] = i - m;
return ;
}
void main() {
m = read() - 1; n = read() - 1;
scanf("%s%s", T, S);
process();
printf("%d
", cnt);
rep(i, 1, cnt) printf("%d%c", can[i] + 1, i < cnt ? ' ' : '
');
return ;
}
}
int main() {
String::main();
return 0;
}