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这里有没有被标记过是一段字符串的结尾
      }
      

应用

  • 检索字符串

    • P2580 于是他错误的点名开始了

      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 Sum

      P4551 最长异或路径

      P4551 最长异或路径 题解 - 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;
      }
      
posted @ 2024-02-06 09:46  加固文明幻景  阅读(12)  评论(0编辑  收藏  举报