BZOJ 4503 [两个串]

传送门
这真的是个黑科技了。
以后忘了怎么写KMP就写FFT了。


题目大意

给出两个长度不超过$10^5$的由小写英文字符构成的字符串$S,T$,询问$T$在$S$中出现了多少次及每次出现的位置(下标从$0$开始)。
$T$中可能存在?通配符,可以匹配任何英文字符。

解析

由于存在通配符,普通的KMP什么的是肯定不行了。
考虑以下卷积:
$$C_k=\sum_{i+j=k}(S_i-T_j)^2\times T_j$$
如果$C_k=0$,那么只可能是对于每一个$j\in[1,k-1]$,只有可能是$T_j=0$或者$S_{k-j}=T_k$。
所以我们把$T$串翻转过来,并且将?的位置设为$0$,再做一下卷积。这个时候,如果存在一个$k\ge m$并且$C_k=0$,那么$k-m$就是一个匹配上的位置了。
怎么做卷积呢?我们拆一些式子,发现最终$C_k$变成了这个东西:
$$C_k=\sum_{i+j=k}(S_i^2)\times T_j + \sum_{i\in[1,k]}T_j^3 -2\sum_{i+j=k}S_i\times(T_j^2)$$
前一部分和最后一个部分直接是卷积,中间一个前缀和。
时间复杂度$O(n log_2n)$,虽然比KMP慢蛮多,但是适用性更广,个人觉得也挺好理解的。
注意FFT精度掉的不少,$eps$开到了$10^{-2}$。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#define DREP(i, s, e) for(int i = s; i >= e ;i--)
#define REP(i, s, e) for(int i = s; i <= e ;i++)

#define DEBUG fprintf(stderr, "Passing [%s] in Line %d\n", __FUNCTION__, __LINE__)
#define chkmax(a, b) a = max(a, b)
#define chkmin(a, b) a = min(a, b)

#include <iostream>
#include <complex>
#include <cstring>
#include <cstdio>
#include <cmath>
#define double long double

using namespace std;
const int maxn = 1e5 + 10, maxN = (1<<18) + 10;
const double pi = acos(-1), eps = 1e-2;

char s[maxn], t[maxn];
int n, m, S[maxn], S2[maxn], T[maxn], T2[maxn], T3[maxn], sum[maxn];

struct Complex
{
double a, b;
Complex(double _a = 0, double _b = 0) : a(_a), b(_b) {}
Complex operator + (Complex B) {return Complex(a + B.a, b + B.b);}
Complex operator - (Complex B) {return Complex(a - B.a, b - B.b);}
Complex operator * (Complex B) {return Complex(a * B.a - b * B.b, a * B.b + b * B.a);}
Complex operator *= (Complex B) {return *this = *this * B;}
};

int L, len, R[maxN];
Complex F1[maxN], F2[maxN];

void FFT(Complex a[], int flag)
{
REP(i, 0, len - 1)
if (i < R[i]) swap(a[i], a[R[i]]);
for (int i = 1; i < len; i <<= 1)
{
Complex T(cos(pi / i), flag * sin(pi / i));
for (int k = 0; k < len; k += (i << 1))
{
Complex t(1, 0);
for (int l = 0; l < i; l++, t *= T)
{
Complex x(a[k+l]), y(t * a[k+l+i]);
a[k + l] = x + y;
a[k + l + i] = x - y;
}
}
}
if (flag < 0)
REP(i, 0, len - 1) a[i].a /= 4*len;
}

int ans[maxn];

signed main()
{
#ifdef CraZYali
freopen("4503.in", "r", stdin);
freopen("4503.out", "w", stdout);
#endif
scanf("%s\n%s", s + 1, t + 1);
n = strlen(s + 1);
m = strlen(t + 1);
REP(i, 1, n)
{
S[i] = s[i] - 'a' + 1;
S2[i] = S[i] * S[i];
}
REP(i, 1, m)
{
T[i] = t[m - i + 1] == '?' ? 0 : t[m - i + 1] - 'a' + 1;
T2[i] = T[i] * T[i];
T3[i] = T2[i] * T[i];
sum[i] = sum[i-1] + T3[i];
}
REP(i, m + 1, n) sum[i] = sum[i-1];
len = 1;
while (len <= n + n) len <<= 1;
L = log2(len);
REP(i, 0, len) R[i] = (R[i >> 1] >> 1) | ((i & 1) << L - 1);
REP(i, 1, n) F1[i] = Complex(S2[i] + T[i], S2[i] - T[i]);
REP(i, 1, n) F2[i] = Complex(S[i] + T2[i], S[i] - T2[i]);
FFT(F1, 1);FFT(F2, 1);
REP(i, 0, len-1) F1[i] *= F1[i], F2[i] *= F2[i];
FFT(F1, -1);FFT(F2, -1);
REP(k, 1, n)
{
double c = F1[k+1].a + sum[k] - 2. * F2[k+1].a;
if (k-m >= 0 && fabs(c)<eps) ans[++ans[0]] = k-m;
}
cout << ans[0] << endl;
REP(i, 1, ans[0])printf("%d\n", ans[i]);
return 0;
}

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×