Palindrome Tree (Eertree)
Palindrome Tree (hay Eertree) là cấu trúc dữ liệu lưu trữ tất cả chuỗi con palindrome phân biệt của một xâu, được xây dựng trực tuyến (online) trong .
Tính chất quan trọng: Một xâu độ dài có tối đa chuỗi con palindrome phân biệt (không tính trùng lặp). Palindrome Tree lưu đúng node (2 node gốc đặc biệt + tối đa palindrome).
Ứng dụng:
- Đếm số palindrome con phân biệt.
- Đếm số lần xuất hiện của mỗi palindrome con.
- Tìm palindrome con dài nhất kết thúc tại mỗi vị trí.
- Phân rã xâu thành palindrome (eertree DP).
Cấu trúc
Palindrome Tree có hai node gốc đặc biệt:
- Root -1: palindrome giả độ dài (gốc cho palindrome lẻ).
- Root 0: palindrome rỗng độ dài (gốc cho palindrome chẵn). Suffix link của nó trỏ về root .
Mỗi node lưu:
len[u]: độ dài palindrome.link[u]: suffix link — palindrome suffix dài nhất thực sự (khác chính nó).to[u][c]: cạnh — palindrome mới khi thêm ký tự vào hai đầu.
Cài đặt
struct PalindromTree {
static const int ALPHA = 26;
struct Node {
int len, link;
int to[ALPHA];
long long cnt; // số lần xuất hiện
};
vector<Node> t;
string s;
int last; // node tương ứng palindrome suffix dài nhất hiện tại
PalindromTree() {
t.push_back({-1, 0, {}, 0}); // node 0: root -1
t.push_back({ 0, 0, {}, 0}); // node 1: root 0
fill(t[0].to, t[0].to + ALPHA, -1);
fill(t[1].to, t[1].to + ALPHA, -1);
t[1].link = 0;
last = 1;
s = "@"; // sentinel (không thuộc alphabet)
}
// Tìm suffix link phù hợp để thêm ký tự c tại vị trí i
int get_link(int v, int i) {
while (s[i - t[v].len - 1] != s[i]) v = t[v].link;
return v;
}
void add(char c) {
s += c;
int i = s.size() - 1;
int cur = get_link(last, i);
int x = c - 'a';
if (t[cur].to[x] == -1) {
// Tạo node mới
Node nw;
nw.len = t[cur].len + 2;
nw.cnt = 0;
fill(nw.to, nw.to + ALPHA, -1);
// Suffix link của node mới
if (nw.len == 1) {
nw.link = 1; // palindrome độ dài 1 → suffix link là root 0
} else {
int tmp = get_link(t[cur].link, i);
nw.link = t[tmp].to[x];
}
t[cur].to[x] = t.size();
t.push_back(nw);
}
last = t[cur].to[x];
t[last].cnt++;
}
// Tính số lần xuất hiện thực sự (truyền ngược suffix link)
void count() {
for (int i = t.size() - 1; i >= 2; i--)
t[t[i].link].cnt += t[i].cnt;
}
};
Ví dụ sử dụng
int main() {
string s; cin >> s;
PalindromTree pt;
for (char c : s) pt.add(c);
pt.count();
// Số palindrome con phân biệt
cout << pt.t.size() - 2 << "\n";
// Liệt kê tất cả palindrome con phân biệt với số lần xuất hiện
for (int i = 2; i < (int)pt.t.size(); i++) {
// pt.t[i].len = độ dài palindrome
// pt.t[i].cnt = số lần xuất hiện
}
}
Ví dụ: Phân rã xâu thành ít palindrome nhất
// dp[i] = số palindrome ít nhất để phân rã s[0..i-1]
// series_link[v]: suffix link "series" để nhảy nhanh trong chuỗi palindrome cùng diff
PalindromTree pt;
vector<int> dp(s.size() + 1, INT_MAX);
dp[0] = 0;
for (int i = 0; i < (int)s.size(); i++) {
pt.add(s[i]);
for (int v = pt.last; v > 1; v = pt.t[pt.t[v].link].to[...]) {
if (dp[i + 1 - pt.t[v].len] != INT_MAX)
dp[i + 1] = min(dp[i + 1], dp[i + 1 - pt.t[v].len] + 1);
}
}
Độ phức tạp
| Độ phức tạp | |
|---|---|
| Xây dựng | bộ nhớ, thời gian |
| Số node | |
| Đếm số lần xuất hiện | sau khi build |
Khi nào dùng Palindrome Tree?
- Cần thông tin về tất cả palindrome con của xâu.
- Bài DP liên quan đến palindrome con (phân rã, đếm).
- Thay thế Manacher khi cần lưu trữ, không chỉ tìm palindrome dài nhất.
Bình luận