【字符串】【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], '\n', 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;
}