字典树

引入

字典树(trie),顾名思义就是在字典上建树用于维护字符串的前缀相关内容。

在字典树中,每条边都是一个字符,从根节点到任意一个节点都可以代表一个字符串(例如 \(1 \rightarrow 2 \rightarrow 6 \rightarrow 11\) 就代表了 \(aba\))。

模板题:P8306 【模板】字典树

结构

大致结构如上图。

对于一个节点,它需要记录它的儿子的编号,必要时还需要记录这个节点在加入字符串的操作中访问了几次(即有几个字符串有这个前缀)。

trie[i][j] 表示节点 \(i\) 走一条边权为 \(j\) 后到达的节点编号,如果不存在则设为 \(0\)

边权则使用离散化思想处理,例如 0~25 分别对应着 a~z26~51 表示 A~Z 等等。

加入字符串

id(char c) 表示 \(c\) 在离散化后的整数。

在处理新加入的字符串 \(s\) 时,从前往后枚举每一位,假设当前节点为 \(x\),处理到了字符串的第 \(i\) 位,那么如果 trie[x][id(s[i])]\(0\),则新增一个节点,将 \(x\) 设为新增节点编号,否则 x = trie[x][id(s[i])]

int id (char c) { // 字符映射规则
  return (c >= 'a' && c <= 'z' ? c - 'a' : (c >= 'A' && c <= 'Z' ? 26 + c - 'A' : 52 + c - '0'));
}

void Insert (string s) { // 加入一个字符串
  int now = 0; // 当前节点编号
  for (int i = 0; i < s.size(); i++) {
    if (!trie[now][id(s[i])]) { // 没有对应节点
      trie[now][id(s[i])] = ++cnt; // 新建节点
    }
    now = trie[now][id(s[i])], sum[now]++; // 跳过去,统计数量 + 1
  }
}

查询字符串

与加入差不多,但当没有对应节点时不应该新建节点,而是直接返回 \(0\)

int Query (string s) { // 查询有多少个字符串以 s 为前缀
  int now = 0; // 当前节点编号
  for (int i = 0; i < s.size(); i++) {
    if (!trie[now][id(s[i])]) { // 没有对应节点
      return 0; // 直接返回
    }
    now = trie[now][id(s[i])]; // 跳过去
  }
  return sum[now]; // 返回结果
}

模板题 Code

#include <iostream>

using namespace std;

const int N = 3e6 + 10;

int t, trie[N][65], sum[N], cnt, n, q;
string s;

int id (char c) { // 字符映射规则
  return (c >= 'a' && c <= 'z' ? c - 'a' : (c >= 'A' && c <= 'Z' ? 26 + c - 'A' : 52 + c - '0'));
}

void Insert (string s) { // 加入一个字符串
  int now = 0; // 当前节点编号
  for (int i = 0; i < s.size(); i++) {
    if (!trie[now][id(s[i])]) { // 没有对应节点
      trie[now][id(s[i])] = ++cnt; // 新建节点
    }
    now = trie[now][id(s[i])], sum[now]++; // 跳过去,统计数量 + 1
  }
}

int Query (string s) { // 查询有多少个字符串以 s 为前缀
  int now = 0; // 当前节点编号
  for (int i = 0; i < s.size(); i++) {
    if (!trie[now][id(s[i])]) { // 没有对应节点
      return 0; // 直接返回
    }
    now = trie[now][id(s[i])]; // 跳过去
  }
  return sum[now]; // 返回结果
}

void Solve () {
  cnt = 0;
  cin >> n >> q;
  for (int i = 1; i <= n; i++) {
    cin >> s;
    Insert(s);
  }
  while (q--) {
    cin >> s;
    cout << Query(s) << '\n';
  }
  for (int i = 0; i <= cnt; i++) {
    for (int j = 0; j < 62; j++) {
      trie[i][j] = 0;
    }
    sum[i] = 0;
  }
}

int main () {
  ios::sync_with_stdio(0), cin.tie(0);
  for (cin >> t; t; t--) {
    Solve();
  }
  return 0;
}

应用

  • 检索字符串,例如模板题。
  • 01 字典树,但是不会。
  • AC 自动姬,但是也不会。
posted @ 2023-10-08 00:43  wnsyou  阅读(7)  评论(0编辑  收藏  举报