【字符串】【P5830】 【模板】失配树
Description
给定一个长度为 (n) 的字符串 (S),有 (m) 次询问,每次询问给定 (S) 的两个前缀,求它们的最长公共 border
的长度。
最长公共 border
的含义为,对于一个字符串 (T),设其 Border
集合为所有既是 (S) 的前缀子串又是 (S) 的后缀子串的集合,两个字符串的最长公共 border
为两个字符串的 Border
集合的交集中长度最长的字符串。
Limitations
(1 leq n leq 10^6)
(1 leq m leq 10^5)
Solution
注意,这篇题解不是这个模板的标准做法,也不是最简单的做法。
两个前缀的最长公共 border 即为他们在 border 树上的 LCA
因为刚起床就被 fa姐姐
拉来验题,脑袋昏昏忘记了这个结论,只能再口胡一个铁憨憨做法。
注意到所求的 border
一定既是第一个字符串的后缀,又是第二个字符串的后缀,因此一定是两个字符串的公共后缀 ,同时注意到由于这两个字符串的前缀是相同的,所以如果一个字符串 (T) 既是其中任意一个串的 border
,又是两个串的公共后缀,那么它一定是两个串的公共 border
。并且这个条件显然也是必要条件,因此我们在求出两串的 lcp
以后只需要在其中任意一个串上找到其最长的长度不超过 lcp
长度的 border
,那么该串即为两串的最长公共 border
。
假设我们已经求出了两串的 lcp
长度,那么问题就只剩下对一个字符串求其最长的长度不超过某数的 border
。
我们考虑对每个前缀,将它向它的最长 border
连一条边,那么显然这个图有 ((n + 1)) 个节点, (n) 条边,又因为这个图是联通的,根据树的判定定理,这个图是一棵树,若规定 (0) 是这棵树的根,数学归纳可得每个节点的父节点为该节点所代表的前缀的最长 border
。因为一个节点的 border
显然比该节点的长度小,所以任何一个节点到根所在的链上,若将节点按深度从小到大排列,则其所代表的前缀长度一定是单调递增的。因此我们只需要对整棵树进行 dfs
,同时用一个栈维护当前节点到根的链,然后在栈里二分即可找到所求的串。
求 border
的方法见 【P3375】KMP字符串匹配。
而求两个前缀的 lcp
,可以对原串建立一个 SAM
,两个前缀在 parent
树上所对应节点的 LCA
即为他们的 lcp
。也可以将原串反过来,转化为求两个后缀的最长公共前缀,求出 SA
后用 height
数组解决。
但是扶苏既不愿意将原串反过来求 SA
在写个 ST
,也担心毒瘤出题人卡了空间以后 SAM
建出来会爆空间,因此扶苏选择了 二分+hash 求出其 lcp
。
显然公共后缀的长度满足二分性,因此只要选择一个满足前缀可减性的 hash
函数就可以 (O(1)) check 了。
考虑时间复杂度:二分求 lcp
的复杂度是 (O(m log n)),在 border
树上二分的复杂度是 (O(m log n)),因此总时间复杂度 (O(n + m log n))。
Code
本来扶苏写了个四模数 hash
,然后被卡常了就尝试减少模数个数,最后发现单模数就可以了(雾
#include <cstdio>
#include <vector>
#include <algorithm>
const int maxh = 4;
const int maxm = 100005;
const int maxn = 1000005;
const int MOD[] = {998244353, 1000000007, 1000000009, 1145141};
int n, m, top = -1;
char S[maxn];
int border[maxn], ans[maxm], stk[maxn];
std::vector<int> son[maxn], query[maxn];
struct HASH {
int md;
ll hash[maxn], inv[maxn];
ll mpow(const int a, int d, const int p) {
ll ret = 1, tmp = a;
while (d) {
if (d & 1) {
(ret *= tmp) %= p;
}
(tmp *= tmp) %= p;
d >>= 1;
}
return ret;
}
void build(const int x) {
md = x;
ll tmp = 1, iv = mpow(100, x - 2, x);
inv[0] = 1;
for (int i = 1; i <= n; ++i) {
hash[i] = (hash[i - 1] + (S[i] - 'a') * tmp) % md;
inv[i] = inv[i - 1] * iv % md;
(tmp *= 100) %= md;
}
}
bool check(const int x, const int y, const int len) {
ll h1 = (hash[x] - hash[x - len]) * inv[x - len] % md, h2 = (hash[y] - hash[y - len]) * inv[y - len] % md;
if (h1 < 0) h1 += md;
if (h2 < 0) h2 += md;
if (h1 != h2) {
return false;
} else {
return true;
}
}
};
HASH h[maxh];
int ReadStr(char *p);
void dfs(const int u);
int main() {
freopen("1.in", "r", stdin);
n = ReadStr(S);
for (int i = 0; i < maxh; ++i) {
h[i].build(MOD[i]);
}
for (int i = 2, j = 0; i <= n; ++i) {
while (j && (S[j + 1] != S[i])) {
j = border[j];
}
if (S[j + 1] == S[i]) {
++j;
}
son[border[i] = j].push_back(i);
}
son[0].push_back(1);
qr(m);
for (int p, q, Ans, i = 1; i <= m; ++i) {
p = q = Ans = 0; qr(p); qr(q);
for (int l = 1, r = std::min(p, q) - 1, mid = (l + r) >> 1; l <= r; mid = (l + r) >> 1) {
bool flag = true;
for (int i = 0; i < maxh; ++i) if ((flag = h[i].check(p, q, mid)) == false) {
break;
}
if (flag) {
l = (Ans = mid) + 1;
} else {
r = mid - 1;
}
}
ans[i] = Ans;
query[std::min(p, q)].push_back(i);
}
dfs(0);
for (int i = 1; i <= m; ++i) {
qw(ans[i], '
', true);
}
return 0;
}
int ReadStr(char *p) {
auto beg = p;
do *(++p) = IPT::GetChar(); while ((*p >= 'a') && (*p <= 'z'));
*p = 0;
return p - beg - 1;
}
void dfs(const int u) {
stk[++top] = u;
for (auto v : query[u]) {
int w = ans[v]; ans[v] = 0;
for (int l = 1, r = top, mid = (l + r) >> 1; l <= r; mid = (l + r) >> 1) if (stk[mid] <= w) {
ans[v] = stk[mid];
l = mid + 1;
} else {
r = mid - 1;
}
}
for (auto v : son[u]) {
dfs(v);
}
--top;
}