Skip to content

Commit 3f4fc7c

Browse files
committed
add palindromic tree
1 parent e9370d5 commit 3f4fc7c

File tree

4 files changed

+211
-0
lines changed

4 files changed

+211
-0
lines changed

string/palindromic_tree.hpp

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#pragma once
2+
3+
#include <map>
4+
#include <string>
5+
#include <vector>
6+
7+
// Palindromic tree / Eertree (回文木)
8+
namespace palindromic_tree {
9+
10+
template <class Key> class Node {
11+
int suffix_link_; // このノードからのsuffix link (suffix の最長回文)
12+
int length_; // このノードが表す回文の長さ。 -1 となる場合もあるので注意
13+
std::map<Key, int> children;
14+
15+
public:
16+
explicit Node(int suffix_link, int length) : suffix_link_(suffix_link), length_(length) {}
17+
18+
int suffix_link() const { return suffix_link_; }
19+
20+
int length() const { return length_; }
21+
22+
int get_child(Key c) const {
23+
auto it = children.find(c);
24+
return (it == children.end()) ? -1 : it->second;
25+
}
26+
27+
void set_child(int c, int nxt_idx) { children[c] = nxt_idx; }
28+
29+
template <class OStream> friend OStream &operator<<(OStream &os, const Node &node) {
30+
os << "Node(suffix_link=" << node.suffix_link() << ", length=" << node.length()
31+
<< ", children={";
32+
for (const auto &[c, nxt] : node.children) os << c << "->" << nxt << ", ";
33+
return os << "})";
34+
}
35+
};
36+
37+
// Palindromic tree
38+
// nodes[0] は長さ -1, nodes[1] は長さ 1 のダミーノード
39+
template <class Key> struct Tree {
40+
std::vector<Node<Key>> nodes;
41+
42+
Tree() { nodes = {Node<Key>(-1, -1), Node<Key>(0, 0)}; }
43+
44+
// nodes[cursor] は s[0:i] の suffix palindrome を表す
45+
// 本関数はその nodes[cursor] の suffix palindrome であって更に s[0:(i + 1)] の suffix link となりうる最長のものを返す
46+
int find_next_suffix(const std::vector<Key> &s, int i, int cursor) {
47+
while (true) {
48+
if (cursor < 0) return 0;
49+
50+
const int cur_len = nodes.at(cursor).length();
51+
const int opposite_pos = i - cur_len - 1;
52+
if (opposite_pos >= 0 and s.at(opposite_pos) == s.at(i)) return cursor;
53+
cursor = nodes.at(cursor).suffix_link();
54+
}
55+
}
56+
57+
// 文字列 s を追加する。 Complexity: O(|s|)
58+
// callback(i, cursor) は s[0:(i + 1)] が追加された後の nodes[cursor] に対して行う処理
59+
template <class Callback> void add_string(const std::vector<Key> &s, Callback callback) {
60+
int cursor = 1;
61+
62+
for (int i = 0; i < (int)s.size(); ++i) {
63+
64+
cursor = find_next_suffix(s, i, cursor);
65+
66+
int ch = nodes.at(cursor).get_child(s.at(i));
67+
68+
if (ch < 0) {
69+
const int nxt_cursor = nodes.size();
70+
const int new_length = nodes.at(cursor).length() + 2;
71+
72+
int new_suffix_link_par = find_next_suffix(s, i, nodes.at(cursor).suffix_link());
73+
int new_suffix_link = nodes.at(new_suffix_link_par).get_child(s.at(i));
74+
if (new_suffix_link < 0) new_suffix_link = 1;
75+
76+
nodes.at(cursor).set_child(s.at(i), nxt_cursor);
77+
nodes.push_back(Node<Key>(new_suffix_link, new_length));
78+
cursor = nxt_cursor;
79+
80+
} else {
81+
cursor = ch;
82+
}
83+
84+
callback(i, cursor);
85+
}
86+
}
87+
88+
template <class Callback> void add_string(const std::string &s, Callback callback) {
89+
add_string(std::vector<Key>{s.cbegin(), s.cend()}, callback);
90+
}
91+
92+
template <class Vec> void add_string(const Vec &s) {
93+
add_string(s, [](int, int) {});
94+
}
95+
};
96+
97+
} // namespace palindromic_tree

string/palindromic_tree.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
title: Palindromic tree / eertree (回文木)
3+
documentation_of: ./palindromic_tree.hpp
4+
---
5+
6+
文字列に現れる回文を効率的に管理するデータ構造.与えられた文字列 $S$ に対して $O(|S| \log \sigma)$ $(\sigma = |\Sigma|)$ で構築可能.
7+
8+
## eertree とは
9+
10+
- 各頂点が,与えられた文字列の(連続)部分文字列である distinct な回文に対応する.
11+
- 例外として,長さ $-1$, $0$ の空文字列を表現するダミーの頂点がそれぞれ存在する.したがって頂点数は正確には回文の種類数 +2 となる.
12+
- 頂点数は $|S| + 2$ 以下である.
13+
- 各頂点から,その回文の suffix である最長回文の頂点への辺 (suffix link) が生えている.
14+
- 例外として,長さ $0$ の空文字列のダミー頂点の suffix link は長さ $-1$ の空文字列へ生えている.
15+
- 長さ $-1$ の空文字列の suffix link は存在しない.つまり, suffix link を辺としたグラフはこの頂点を根とする根付き木となる.
16+
17+
## 使用方法
18+
19+
```cpp
20+
string S = "sakanakanandaka";
21+
palindromic_tree::Tree<char> tree;
22+
tree.add_string(S);
23+
24+
// コールバック関数も使用可能.
25+
// 第一引数は S 上の index (0, ..., |S| - 1), 第二引数は tree.nodes の index.
26+
// 1 文字読む毎に tree 上のどこにいるか分かるので出現回数カウント等が可能.
27+
vector<int> dp(S.size() + 2);
28+
auto callback = [&](int str_idx, int node_idx) -> void { dp.at(node_idx)++; };
29+
30+
tree.add_string(S, callback);
31+
```
32+
33+
## 問題例
34+
35+
- [No.263 Common Palindromes Extra - yukicoder](https://yukicoder.me/problems/no/263)
36+
- [No.2606 Mirror Relay - yukicoder](https://yukicoder.me/problems/no/2606)
37+
38+
## 参考文献・リンク
39+
40+
- [1] M. Rubinchik and A. M. Shur, "EERTREE: An efficient data structure for processing palindromes in strings," European Journal of Combinatorics 68, 249-265, 2018. [arXiv](https://arxiv.org/abs/1506.04862)
41+
- [Palindromic Tree - math314のブログ](https://math314.hateblo.jp/entry/2016/12/19/005919)
42+
- [すごい!EERTREE!いごす - 誤読](https://mojashi.hatenablog.com/entry/2017/07/17/155520)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#define PROBLEM "https://yukicoder.me/problems/no/2606"
2+
#include "../palindromic_tree.hpp"
3+
4+
#include <algorithm>
5+
#include <iostream>
6+
#include <string>
7+
#include <vector>
8+
9+
using namespace std;
10+
11+
int main() {
12+
cin.tie(nullptr), ios::sync_with_stdio(false);
13+
14+
string S;
15+
cin >> S;
16+
17+
palindromic_tree::Tree<char> tree;
18+
19+
vector<long long> visitcnt(S.size() + 2);
20+
tree.add_string(S, [&](int, int node_idx) { visitcnt.at(node_idx)++; });
21+
22+
const int V = tree.nodes.size();
23+
for (int i = V - 1; i > 0; --i) visitcnt.at(tree.nodes.at(i).suffix_link()) += visitcnt.at(i);
24+
25+
vector<vector<int>> children(V);
26+
for (int i = 1; i < V; ++i) children.at(tree.nodes.at(i).suffix_link()).push_back(i);
27+
28+
vector<long long> dp(V, 0);
29+
for (int i = 0; i < V; ++i) {
30+
dp.at(i) += visitcnt.at(i) * max(tree.nodes.at(i).length(), 0);
31+
for (int ch : children.at(i)) dp.at(ch) += dp.at(i);
32+
}
33+
34+
cout << *max_element(dp.begin(), dp.end()) << '\n';
35+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#define PROBLEM "https://yukicoder.me/problems/no/263"
2+
#include "../palindromic_tree.hpp"
3+
4+
#include <iostream>
5+
#include <string>
6+
#include <vector>
7+
8+
using namespace std;
9+
10+
int main() {
11+
cin.tie(nullptr), ios::sync_with_stdio(false);
12+
13+
string S, T;
14+
cin >> S >> T;
15+
16+
palindromic_tree::Tree<char> tree;
17+
18+
vector<long long> visitcnt(S.size() + T.size() + 2);
19+
20+
tree.add_string(S, [&](int, int node_idx) { visitcnt.at(node_idx)++; });
21+
tree.add_string(T);
22+
23+
const int V = tree.nodes.size();
24+
for (int v = V - 1; v > 0; --v) {
25+
visitcnt.at(tree.nodes.at(v).suffix_link()) += visitcnt.at(v);
26+
}
27+
28+
// 0 と 1 はダミーなのでカウントしてはいけない
29+
visitcnt.at(0) = visitcnt.at(1) = 0;
30+
31+
for (int v = 1; v < V; ++v) visitcnt.at(v) += visitcnt.at(tree.nodes.at(v).suffix_link());
32+
33+
long long ret = 0;
34+
tree.add_string(T, [&](int, int node_idx) { ret += visitcnt.at(node_idx); });
35+
36+
cout << ret << '\n';
37+
}

0 commit comments

Comments
 (0)