SP1811 LCS - Longest Common Substring
知識點: SA,單調棧
原題面 Luogu
題意簡述
給定兩字串 \(S_1, S_2\),求它們的最長公共子串長度。
\(|S_1|,|S_2|\le 2.5\times 10^5\)。
分析題意
只有兩個字串,考慮 SA。
\(S_1\) 加個終止符,\(S_2\) 拼接到 \(S_1\) 後面,跑 SA 求出 \(\operatorname{height}\)。
問題變為 求前半段字尾 與 後半段字尾 \(\operatorname{lcp}\) 的最大值。
\(i\) 為後半段的字尾 等價於 \(i>|S_1|+1\)。
對於一個字尾 \(i>|S_1|+1\),設 \(l_i\)
即有 \(sa_{l_i}\le |S_1|,\ l+i<rk_i\),且 \(\forall l_i<k<rk_i, sa_k>|S_1|+1\) 成立。
類似地,設 \(r_i\) 為字尾排序後 最小 的 比 \(i\) 大 的 前半段 的字尾的 排名。
考慮 \(\operatorname{lcp}\) 的單調性。
對於一個後半段的字尾 \(i>|S_1|+1\),滿足 \(\operatorname{lcp}(i,j)\) 最大的 \(j\le|S_1|\),顯然為 \(l_i\)
\[\max\{\operatorname{lcp}(i,j)\} = \max\{\operatorname{lcp}(l_i, i), \operatorname{lcp}(i, r_i)\} \]
則有:
\[ans = \max_{i=|S_1|+2}^{|S_1|+|S_2|+1}\max\{\operatorname{lcp}(l_i, i), \operatorname{lcp}(i, r_i)\} \]
先預處理 \(\operatorname{lcp}\) 建立 st 表。
\(l_i,r_i\) 可通過單調棧簡單求得,計算答案時列舉後半段字尾,\(O(1)\)
總複雜度 \(O(n\log n)\) 級別。
一些細節
若 \(l_i<1\) 時,該 \(l_i\) 不作出貢獻,因為不存在這樣的字尾。
\(r_i>|S|\) 時也沒有貢獻,這樣的字尾已經屬於後半段了。
爆零小技巧:\(\log\) 函式賊 jier 慢,建議預處理 \(\log_2\) 函式。
程式碼實現
//知識點:SA,單調棧
/*
By:Luckyblock
*/
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <ctype.h>
#define ll long long
const int kMaxn = 5e5 + 10;
//=============================================================
char S[kMaxn];
int n1, n, m, ans, cnt[kMaxn], id[kMaxn], rkid[kMaxn];
int sa[kMaxn], rk[kMaxn << 1], oldrk[kMaxn << 1], height[kMaxn];
int MaxHeight[kMaxn][20], Log2[kMaxn];
int top, st[kMaxn], L[kMaxn], R[kMaxn];
//=============================================================
inline int read() {
int f = 1, w = 0; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
return f * w;
}
void GetMax(int &fir, int sec) {
if (sec > fir) fir = sec;
}
bool cmp(int x, int y, int w) {
return oldrk[x] == oldrk[y] &&
oldrk[x + w] == oldrk[y + w];
}
void GetHeight() {
for (int i = 1, k = 0; i <= n; ++ i) {
if (rk[i] == 1) k = 0;
else {
if (k > 0) k --;
int j = sa[rk[i] - 1];
while (i + k <= n && j + k <= n &&
S[i + k] == S[j + k]) {
++ k;
}
}
height[rk[i]] = k;
}
}
int Query(int l_, int r_) {
int k = Log2[r_ - l_ + 1];
return std :: min(MaxHeight[l_][k], MaxHeight[r_ - (1 << k) + 1][k]);
}
void MakeSt() {
for (int i = 2; i <= n; ++ i) MaxHeight[i][0] = height[i];
for (int i = 2; i <= n; ++ i) {
Log2[i] = Log2[i - 1] + ((1 << Log2[i - 1] + 1) == i);
}
for (int j = 1; j < 20; ++ j) {
for (int i = 1; i + (1 << j) - 1 <= n; ++ i) {
MaxHeight[i][j] = std :: min(MaxHeight[i][j - 1],
MaxHeight[i + (1 << j - 1)][j - 1]);
}
}
}
void SuffixSort() {
m = 300;
for (int i = 1; i <= n; ++ i) ++ cnt[rk[i] = S[i]];
for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; -- i) sa[cnt[rk[i]] --] = i;
for (int p, w = 1; w < n; w <<= 1) {
p = 0;
for (int i = n; i > n - w; -- i) id[++ p] = i;
for (int i = 1; i <= n; ++ i) {
if (sa[i] > w) id[++ p] = sa[i] - w;
}
memset(cnt, 0, sizeof (cnt));
for (int i = 1; i <= n; ++ i) ++ cnt[(rkid[i] = rk[id[i]])];
for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
for (int i = n; i >= 1; -- i) sa[cnt[rkid[i]] --] = id[i];
std ::swap(rk, oldrk);
m = 0;
for (int i = 1; i <= n; ++ i) {
m += (cmp(sa[i], sa[i - 1], w) ^ 1);
rk[sa[i]] = m;
}
}
GetHeight();
MakeSt();
}
//=============================================================
int main() {
scanf("%s", S + 1); n1 = strlen(S + 1);
S[n1 + 1] = 'z' + 1;
scanf("%s", S + n1 + 1 + 1); n = strlen(S + 1);
SuffixSort();
//注意 l,r 存的是排名,列舉時按照字典序列舉字尾。
for (int i = 1, now = 0; i <= n; ++ i) {
if (sa[i] == n1 + 1) continue ;
if (sa[i] > n1 + 1) {
L[i] = now, st[++ top] = i;
continue ;
}
for (; top; top --) R[st[top]] = i;
now = i;
}
for (; top; top --) R[st[top]] = n1 + 1;
for (int i = 1; i <= n; ++ i) {
if (sa[i] <= n1 + 1) continue ;
if (L[i]) GetMax(ans, Query(L[i] + 1, i));
if (R[i] <= n1 + 1) GetMax(ans, Query(i + 1, R[i]));
}
printf("%d", ans);
return 0;
}
/*
aabcab
adadeaf
aabcab{adadeaf
abcab{adadeaf
ab{adadeaf
adadeaf
adeaf
af
bcab{adadeaf
b{adadeaf
cab{adadeaf
dadeaf
deaf
eaf
f
{adadeaf
*/