給定 n 個字串 (n ≤ 5,總長度 ≤ 10000),求它們的**最長公共子串**長度。
[min_len, max_len]。link) 構成長度遞減的樹。match_len[state][k]。min_k match_len[state][k] (受限於最短匹配)。min_k match_len 的最大值。匹配時只能直接更新當前狀態。需沿父鏈向上傳播:
for (p = cur; p; p = p->link) p->match_len[k] = max(p->match_len[k], min(cur_len, p->max_len))
因為若長度 L 在狀態 cur 匹配,則其所有後綴 (父鏈祖先) 也匹配長度 ≤ min(L, ancestor.max_len)。
#include
using namespace std;
struct SAM {
struct State {
int next[26]{};
int link = -1;
int len = 0;
int match_len[6]{}; // n ≤ 5, 索引 1..n
};
vector st;
int last = 0;
SAM(int maxlen = 0) { st.reserve(2 * maxlen); st.push_back(State()); }
void extend(int c) {
int cur = st.size();
st.push_back(State());
st[cur].len = st[last].len + 1;
int p = last;
while (p != -1 && !st[p].next[c]) {
st[p].next[c] = cur;
p = st[p].link;
}
if (p == -1) {
st[cur].link = 0;
} else {
int q = st[p].next[c];
if (st[p].len + 1 == st[q].len) {
st[cur].link = q;
} else {
int clone = st.size();
st.push_back(st[q]); // 複製 q
st[clone].len = st[p].len + 1;
while (p != -1 && st[p].next[c] == q) {
st[p].next[c] = clone;
p = st[p].link;
}
st[q].link = st[cur].link = clone;
}
}
last = cur;
st[cur].match_len[1] = st[cur].len; // 第一個字串自身完全匹配
}
void match_string(const string& s, int idx) {
int p = 0, l = 0;
for (char ch : s) {
int c = ch - 'a';
while (p && !st[p].next[c]) {
p = st[p].link;
l = st[p].len;
}
if (st[p].next[c]) {
p = st[p].next[c];
l++;
}
// 傳播匹配長度
for (int v = p; v; v = st[v].link) {
st[v].match_len[idx] = max(st[v].match_len[idx], min(l, st[v].len));
}
}
}
int solve(int n) {
int ans = 0;
for (auto& state : st) {
int mn = INT_MAX;
for (int i = 1; i <= n; i++) mn = min(mn, state.match_len[i]);
ans = max(ans, mn);
}
return ans;
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
string s;
cin >> s;
SAM sam(s.size());
for (char c : s) sam.extend(c - 'a');
for (int i = 2; i <= n; i++) {
cin >> s;
sam.match_string(s, i);
}
cout << sam.solve(n) << '\n';
return 0;
}
| 細節 | 處理 |
|---|---|
| clone 狀態 match_len 初始化 | 複製 q 時 match_len 也複製,但 clone 非終態,match_len[1] 需重設為 0 (或不參與 min) |
| 第一字串自身匹配 | 構建時 st[cur].match_len[1] = st[cur].len |
| 傳播上界 | min(l, st[v].len) 確保不超過該狀態代表的最長子串 |
| 字元集 | 題目小寫字母,26 字元陣列。通用可用 unordered_map |
SAM 精髓:線性構建、父鏈樹結構、endpos 等價類。多字串公共子串是 SAM 經典應用之一,「在第一字串 SAM 上跑其餘字串、父鏈傳播匹配長度、取 min 再 max」是固定套路,熟練掌握可秒殺同類題目。