給定 n (≤100) 個 DNA 字符串(長度 ≤ 1000),求**最長子串**使其在**超過一半**字符串中出現。若多解,按字典序全部輸出;無解輸出 ?。
'z'+1, 'z'+2, …)將所有字符串拼成一個大串 S。SA 和 Height 數組:SA[i] = 第 i 小後綴起始位置,Height[i] = SA[i] 與 SA[i-1] 的 LCP 長度。belong[pos]:記錄 S 中每個位置屬於第幾個原字符串(分隔符屬於 0)。L:檢查是否存在長度 ≥ L 的子串在 > n/2 個字符串中出現。Height,維護連續段中 Height ≥ L 的後綴集合,統計不同 belong 數量。若 > n/2 返回 true。L 再次掃描,收集所有滿足條件的子串(SA 順序保證字典序)。// 後綴數組構建(倍增算法 O(n log n))
void build_sa(vector<int>& s, int m) { ... }
void get_height(vector<int>& s) { ... }
// 檢查長度 L 是否可行
bool check(int L, int n_str) {
unordered_set<int> seen;
for (int i = 1; i <= N; ++i) {
if (height[i] >= L) seen.insert(belong[sa[i]]);
else { if (seen.size() > n_str/2) return true; seen.clear(); }
}
return seen.size() > n_str/2;
}
// 收集所有長度 L 的解(字典序)
vector<string> collect(int L, int n_str) {
vector<string> ans;
unordered_set<int> seen;
int start = 1;
for (int i = 1; i <= N; ++i) {
if (height[i] >= L) {
seen.insert(belong[sa[i]]);
} else {
if (seen.size() > n_str/2) {
string sub = S.substr(sa[i-1], L);
ans.push_back(sub);
}
seen.clear();
}
}
if (seen.size() > n_str/2) ans.push_back(S.substr(sa[N], L));
sort(ans.begin(), ans.end());
ans.erase(unique(ans.begin(), ans.end()), ans.end());
return ans;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, first = 1;
while (cin >> n, n) {
if (!first) cout << '\n'; first = 0;
string S; vector<int> s; vector<int> belong;
for (int i = 1; i <= n; ++i) {
string t; cin >> t;
for (char c : t) { S += c; s.push_back(c - 'a' + 1); belong.push_back(i); }
S += char('z' + i); s.push_back(27 + i); belong.push_back(0); // 唯一分隔符
}
S += '$'; s.push_back(0);
int N = s.size() - 1;
build_sa(s, 30 + n);
get_height(s);
int L = 0, lo = 1, hi = 1000;
while (lo <= hi) {
int mid = (lo + hi) / 2;
if (check(mid, n)) { L = mid; lo = mid + 1; }
else hi = mid - 1;
}
if (L == 0) { cout << "?\n"; continue; }
auto ans = collect(L, n);
for (auto& x : ans) cout << x << '\n';
}
return 0;
}
O(N log N),N ≤ 100×1000 + 100 ≈ 10⁵。O(log 1000 × N) ≈ 10⁶。O(N log N),POJ 時限 5s 輕鬆通過。'z'+i 確保不與 a-z 衝突,且互不相同。Height ≥ L 對應一組共享前綴 ≥ L 的後綴,統計其 belong 去重計數。SA 本身按字典序排序,掃描順序即為字典序,收集去重即可。POJ 3294 是廣義後綴數組的經典應用:多字符串公共子串問題統一拼接、構建 SA、Height 掃描 + 二分答案。掌握倍增構建 SA、Height 含義、連續段統計不同字符串三大核心,即可解決所有「多串公共子串/子序列」問題。