SP1812 LCS2 - Longest Common Substring II
知識點: SAM
Luogu
題意簡述:
給定不超過 \(10\) 個字串 \(S_1, S_2, \cdots\),求它們最長公共子串的長度。
\(|S_i|\le 10^5\)。
分析題意
多串最長公共子串問題,考慮 SAM。
如果只有兩個串:SP1811 LCS - Longest Common Substring
對第一個串建 SAM,用第二個串從起始節點開始,在 SAM 上進行匹配。
若當前狀態為 \(x\),如果有對應字元 \(s_i\) 的轉移,直接轉移即可,匹配長度 \(+1\)。
如果沒有對應轉移,轉移到 \(\operatorname{link}(x)\),匹配長度 \(=\operatorname{len}(x)+1\)
若始終找不到對應轉移,則從根開始重新匹配。
跳 parnet 樹相當於失配指標,繼續利用了已匹配的部分。
匹配過程中匹配的最長長度即為答案。
考慮多串,對第一個串 \(S_1\) 建 SAM,用其他串在 SAM 上匹配,設當前匹配到串 \(S_i\)。
對於狀態 \(u\),維護轉移到它時最大的匹配長度 \(mx_u\),即以該狀態作為字尾時的公共子串的最長長度。
在匹配過程中進行維護即可。
考慮一個狀態 parent 樹上的所有祖先,若該狀態可被匹配到,則祖先也可被匹配到。
祖先的 \(mx\)
\(S_i\) 匹配完成後按拓撲序對祖先的資訊進行更新。
對於一個狀態 \(u\),將 \(S_2\cdots S_n\) 匹配時的 \(mx_u\) 取 \(\min\),得到在所有字串中 轉移到 \(u\) 最長的匹配長度,即以 \(u\) 為字尾時 \(S_2\cdots S_n\) 公共子串的最長長度,設為 \(mi_u\)。
所有的 \(mi_u\) 取最小值,即為答案。
小細節
最長公共子串不會超過最短串的長度,應對最短的串建 SAM,以保證複雜度。
注意每次匹配完一個字串時,都將 \(mx\) 清空。
更新祖先資訊時 應對祖先的 \(\operatorname{len}\)
程式碼實現
//知識點:SAM
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define ll long long
const int kMaxn = 3e5 + 10;
const int kMaxm = 26;
//=============================================================
char S[11][kMaxn];
int n, T, last = 1, node_num = 1, ans;
int cnt[kMaxn], id[kMaxn];
int ch[kMaxn << 1][kMaxm], len[kMaxn <<1], link[kMaxn << 1];
int minn[kMaxn << 1], maxx[kMaxn << 1];
//=============================================================
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;
}
void GetMin(int &fir, int sec) {
if (sec < fir) fir = sec;
}
void Insert(int c_) {
int p = last, now = last = ++ node_num;
len[now] = len[p] + 1;
for (; p && ! ch[p][c_]; p = link[p]) ch[p][c_] = now;
if (! p) {link[now] = 1; return ;}
int q = ch[p][c_];
if (len[q] == len[p] + 1) {link[now] = q; return ;}
int newq = ++ node_num;
memcpy(ch[newq], ch[q], sizeof(ch[q]));
link[newq] = link[q], len[newq] = len[p] + 1;
link[q] = link[now] = newq;
for (; p && ch[p][c_] == q; p = link[p]) ch[p][c_] = newq;
}
void TopSort() {
for (int i = 1; i <= node_num; ++ i) cnt[len[i]] ++;
for (int i = 1; i <= node_num; ++ i) cnt[i] += cnt[i - 1];
for (int i = 1; i <= node_num; ++ i) id[cnt[len[i]] --] = i;
}
void Work(char *S_) {
int n = strlen(S_ + 1), now = 1, l = 0;
for (int i = 1; i <= n; ++ i) {
while (now && ! ch[now][S_[i] - 'a']) {
now = link[now];
l = len[now];
}
if (! now) {
now = 1;
l = 0;
continue ;
}
++ l;
now = ch[now][S_[i] - 'a'];
GetMax(maxx[now], l);
}
for (int i = node_num; i; -- i) {
int u = id[i], fa = link[u];
GetMax(maxx[fa], std :: min(maxx[u], len[fa]));
GetMin(minn[u], maxx[u]);
maxx[u] = 0; //注意清空,準備下一次匹配。
}
}
//=============================================================
int main() {
int min_len = 1e9 + 2077, min_pos;
while (~ scanf("%s", S[++ T] + 1)) {
int now_len = strlen(S[T] + 1);
if (now_len < min_len) {
min_len = now_len;
min_pos = T;
}
}
std :: swap(S[1], S[min_pos]);
for (int i = 1; i <= min_len; ++ i) Insert(S[1][i] - 'a');
TopSort();
memset(minn, 63, sizeof (minn));
for (int i = 2; i < T; ++ i) Work(S[i]);
for (int i = 1; i <= node_num; ++ i) GetMax(ans, minn[i]);
printf("%d", ans);
return 0;
}