Trie
概述
字典树是一种用于实现快速检索的多叉树结构,它的每个节点都拥有若干个字符指针,若在插入或检索字符串时扫描到一个字符 \(c\) ,就沿着当前节点 \(c\) 的这个字符指针,走向该指针指向的结点。
时间复杂度 \(O(N)\), 空间复杂度 \(O(NC)\)。
实现
-
初始化
-
一棵空字典树仅包含一个根节点,该节点的字符指针均指向空。
-
int trie[SIZE][26], tot = 1;//假设字符串只由小写字母构成 bool end_tag[SIZE];
-
-
插入
-
当需要插入一个字符串 \(S\) 时,我们令一个指针 \(P\) 起初指向根节点。然后,依次扫描 \(S\) 中的每个字符 \(c\):
- 若 \(P\) 的 \(c\) 字符指针指向一个已经存在的结点 \(Q\) ,则令 \(P = Q\)。
- 若 \(P\) 的 \(c\) 字符指针指向空,则新建一个结点 \(Q\),令 \(P\) 的 \(c\) 字符指针指向 \(Q\) ,然后令 \(P = Q\)。
当 \(S\) 中的字符扫描完毕时,在当前节点 \(P\) 上标记它是一个字符串的末尾。
-
void insert(string& str) { int p = 1; for (char ch : str) { int ind = ch - 'a'; if (trie[p][ind] == 0) trie[p][ind] = ++tot;//没有该结点,就插入一个 p = trie[p][ind];//移动指针指向新的父节点 } end_tag[p] = true;//把插入的字符串的末尾打上标记 }
-
-
检索
-
当需要检索一个字符串 \(S\) 在字典树中是否存在时,我们令一个指针 \(P\) 起初指向根节点,然后依次扫描 \(S\) 中的每个字符 \(c\)。
- 若 \(P\) 中的字符指针指向空,则说明 \(S\) 没有被插入过字典树,结束检索。
- 若 \(P\) 中的字符指针指向一个已经存在的结点 \(Q\),则令 \(P=Q\)。
当 \(S\) 中的字符扫描完毕时,若当前节点 \(P\) 被标记为一个字符串的末尾,则说明 \(S\) 在字典树中存在,否则说明 \(S\) 没有被插入过字典树。
-
bool search(string& str) { int p = 1; for (char ch : str) { p = trie[p][ch - 'a']; if (p == 0) return false;//肯定没找到 } return end_tag[p];//判断p这里有没有被标记过是一段字符串的结尾 }
-
应用
-
检索字符串
-
constexpr int SIZE = 500010; int trie[SIZE][26], tot = 1; short end_tag[SIZE]; void insert(string& str) { int p = 1; for (char ch : str) { int ind = ch - 'a'; if (trie[p][ind] == 0) trie[p][ind] = ++tot; p = trie[p][ind]; } end_tag[p] = true; } short search(string& str) { int p = 1; for (char ch : str) { p = trie[p][ch - 'a']; if (p == 0) return 0; } if (end_tag[p] == 1) { end_tag[p] ++; return 1; } return end_tag[p]; } int main() { int n, m; cin >> n; while(n--) { string s; cin >> s; insert(s); } cin >> m; while(m--) { string s; cin >> s; short opt = search(s); if (opt == 1) cout << "OK"; else if (opt == 0) cout << "WRONG"; else cout << "REPEAT"; cout << endl; } return 0; }
-
-
\(01-\text{trie}\)
将数的二进制表示看做一个字符串,就可以建出字符集为 \(\{0,1\}\) 的字典树。
-
维护异或极值
Xor SumP4551 最长异或路径 题解 - zyc2003的笔记集合 - 洛谷博客 (luogu.com.cn)
constexpr int N = 100010; int n; //链式前向星存图 struct Edge { int to, w, next; }edge[N << 1]; int head[N], tot = 0; int dis[N];//dix[x]表示x到根节点的边权抑或和 int trie[N << 4][2], cnt = 1;//字典树 bool end_tag[N << 4];//结束标记 void add(int u, int v, int w) {//建边 tot++; edge[tot].to = v; edge[tot].w = w; edge[tot].next = head[u]; head[u] = tot; } void dfs(int x, int fa) {//预处理出dis数组 for (int i = head[x]; i; i = edge[i].next) { int to = edge[i].to, w = edge[i].w; if (to == fa) continue; dis[to] = (w ^ dis[x]); dfs(to, x); } return ; } void insert(int x) {//字典树插入操作 int p = 1; for (int i = 31; i >= 0; i--) { int digit = x >> i & 1; if (!trie[p][digit]) trie[p][digit] = ++cnt; p = trie[p][digit]; } end_tag[p] = true; } int getSum(int num) { int p = 1, sum = 0; for (int i = 31; i >= 0; i--) { int digit = num >> i & 1; if (trie[p][digit ^ 1])//贪心思路:找与目标位相反的 sum += (1 << i), p = trie[p][digit ^ 1];//找到就更新sum else p = trie[p][digit];//否则往下找 } return sum; } int main() { cin >> n; for (int _ = 1, u, v, w; _ < n; _++) { cin >> u >> v >> w; add(u, v, w); add(v, u, w); } dis[1] = 0; dfs(1, 0); for (int i = 1; i <= n; i++) insert(dis[i]);//把dis数组插入字典树 int sum = 0; for (int i = 1; i <= n; i++) sum = max(sum, getSum(dis[i]));//利用字典树求最大异或值 cout << sum << endl; return 0; }
-
维护异或和
\(01-\text{trie}\) 可以用来维护一些数字的异或和,支持修改(删除 + 重新插入),和全局加(即:让其所维护所有数值递增
1
,本质上是一种特殊的修改操作)题解 P6018 Ynoi2010Fusion tree -舒阳的博客
难度过大,后面再学
#include <bits/stdc++.h> using namespace std; const int _ = 5e5 + 10; namespace trie { const int _n = _ * 25; int rt[_]; int ch[_n][2]; int w[_n]; //`w[o]` 指节点 `o` 到其父亲节点这条边上数值的数量(权值)。 int xorv[_n]; int tot = 0; void maintain(int o) { // 维护w数组和xorv(权值的异或)数组 w[o] = xorv[o] = 0; if (ch[o][0]) { w[o] += w[ch[o][0]]; xorv[o] ^= xorv[ch[o][0]] << 1; } if (ch[o][1]) { w[o] += w[ch[o][1]]; xorv[o] ^= (xorv[ch[o][1]] << 1) | (w[ch[o][1]] & 1); } } int mknode() { // 创造一个新的节点 ++tot; ch[tot][0] = ch[tot][1] = 0; w[tot] = 0; return tot; } void insert(int &o, int x, int dp) { // x是权重,dp是深度 if (!o) o = mknode(); if (dp > 20) return (void)(w[o]++); insert(ch[o][x & 1], x >> 1, dp + 1); maintain(o); } void erase(int o, int x, int dp) { if (dp > 20) return (void)(w[o]--); erase(ch[o][x & 1], x >> 1, dp + 1); maintain(o); } void addall(int o) { // 对所有节点+1即将所有节点的ch[o][1]和ch[o][0]交换 swap(ch[o][1], ch[o][0]); if (ch[o][0]) addall(ch[o][0]); maintain(o); } } // namespace trie int head[_]; struct edges { int node; int nxt; } edge[_ << 1]; int tot = 0; void add(int u, int v) { edge[++tot].nxt = head[u]; head[u] = tot; edge[tot].node = v; } int n, m; int rt; int lztar[_]; int fa[_]; void dfs0(int o, int f) { // 得到fa数组 fa[o] = f; for (int i = head[o]; i; i = edge[i].nxt) { // 遍历子节点 int node = edge[i].node; if (node == f) continue; dfs0(node, o); } } int V[_]; int get(int x) { return (fa[x] == -1 ? 0 : lztar[fa[x]]) + V[x]; } // 权值函数 int main() { cin >> n >> m; for (int i = 1; i < n; i++) { int u, v; cin >> u >> v; add(u, v); // 双向建边 add(rt = v, u); } dfs0(rt, -1); // rt是随机的一个点 for (int i = 1; i <= n; i++) { cin >> V[i]; if (fa[i] != -1) trie::insert(trie::rt[fa[i]], V[i], 0); } while (m--) { int opt, x; cin >> opt >> x; if (opt == 1) { lztar[x]++; if (x != rt) { if (fa[fa[x]] != -1) trie::erase(trie::rt[fa[fa[x]]], get(fa[x]), 0); V[fa[x]]++; if (fa[fa[x]] != -1) trie::insert(trie::rt[fa[fa[x]]], get(fa[x]), 0); // 重新插入 } trie::addall(trie::rt[x]); // 对所有节点+1 } else if (opt == 2) { int v; cin >> v; if (x != rt) trie::erase(trie::rt[fa[x]], get(x), 0); V[x] -= v; if (x != rt) trie::insert(trie::rt[fa[x]], get(x), 0); // 重新插入 } else { int res = 0; res = trie::xorv[trie::rt[x]]; res ^= get(fa[x]); printf("%d\n", res); } } return 0; }
-