Trie树和01Trie树

概要

  • 字典树又叫前缀树,一般用来存储一些单词或数字,在查找的时候比较方便,由于相同前缀只存储一次,所以也是比较省空间的
  • 字典树是一棵树,一般用数组存储比较方便,从根节点出发,根节点不存储字母,每遇到一个字母,先判断树上是不是已经有了,如果有了,就顺着这条路;如果没有,新建立一条路径,直到最后一个字母,这时候将末尾字母所在位置染色,表示这是末尾字母。所以,字典树是一棵多叉树。
    在这里插入图片描述
    字典树思想相对比较容易

插入操作

  • 使用p作为索引,贯穿于整条链,用k来记录节点次序,因为根节点是不存字母的,所以最后染色位置就是最后一个字母位置
void insert(string s){
    int len = s.length();
    int p = 0;
    for(int i=0;i<len;i++){
        char c = s[i] - 'a';
        if(!Trie[p][c]){
            Trie[p][c] = k++;
        }
        p = Trie[p][c];
    }
    color[p] = 1;
}

查找操作

  • 查找一个单词是否在字典树中,只要找到一条和单词吻合的链,且它的末尾被染色即可
bool search(string s){
    int len = s.length();
    int p = 0;
    for(int i=0;i<len;i++){
        char c = s[i] - 'a';
        if(!Trie[p][c]) return false;
        p = Trie[p][c];
    }
    return color[p] == 1;
}

01trie树

  • 就是把数字转化为01串然后按照trie树的操作插入和查找,在一些位运算的题目中有奇效,具体见例题

例题

洛谷P2580

  • 此题完全可用map求解,非常简单,但是此题可作为字典树第一道习题
  • 第几次出现,则字典树相应单词末尾染不同的颜色即可
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iomanip>
#include <queue>
#include <stack>
using namespace std;
typedef long long ll;
const int MAXN = 2e6+100;
int Trie[MAXN][30];
int color[MAXN];
int k = 1;
void insert(string s){
    int len = s.length();
    int p = 0;
    for(int i=0;i<len;i++){
        char c = s[i] - 'a';
        if(!Trie[p][c]){
            Trie[p][c] = k++;
        }
        p = Trie[p][c];
    }
    color[p] = 1;
}
string search(string s){
    int len = s.length();
    int p = 0;
    for(int i=0;i<len;i++){
        char c = s[i] - 'a';
        if(!Trie[p][c]) return "WRONG";
        p = Trie[p][c];
    }
    if(color[p] == 1){
        color[p] = 2;
        return "OK";
    }else return "REPEAT";
}
int main(){
    int n,m;
    cin>>n;
    string s;
    for(int i=0;i<n;i++){
        cin>>s;
        insert(s);
    }
    cin>>m;
    for(int i=0;i<m;i++){
        cin>>s;
        cout<<search(s)<<endl;
    }
    return 0;
}

hdu1251

  • 问以某个单词为前缀的单词数量。使用一个number数组记录单词前缀数量,每次加入一个单词,更新number数组即可
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include <cmath>
#include <iomanip>
#include <queue>
#include <stack>
using namespace std;
typedef long long ll;
const int MAXN = 2e6+100;
int Trie[MAXN][30];
int k = 1;
int color[MAXN]; 
int number[MAXN];
void insert(string s){
    int len = s.length();
    int p = 0;
    for(int i=0;i<len;i++){
        int c = s[i] - 'a';
        if(!Trie[p][c]){
            Trie[p][c] = k;
            k++;
        }
        number[p] ++;
        p = Trie[p][c];
    }
    number[p]++;
}
int search(string s){
    int p = 0;
    int len = s.length();
    for(int i=0;i<len;i++){
        int c = s[i] - 'a';
        if(!Trie[p][c]) return 0;
        p = Trie[p][c];
    }
    return number[p];
}
int main(){
    string s;
    while(true){
        getline(cin,s);
        if(s == "") break;
        insert(s);
    }
    while(cin>>s){
        cout<<search(s)<<endl;
    }
    return 0;
}

前缀统计

  • 在字典树上找即可,但有个小问题,正常应该开 1 e 6 × 26 1e6\times 26 1e6×26的数组,但是内存超了,这题开 1 e 5 × 26 1e5\times 26 1e5×26的数组也能过
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 1e5 + 5;
struct Trie{
  int ch[N][26];
  int color[N];
  int tot;
  Trie(){
    memset(ch, 0, sizeof ch);
    memset(color, 0, sizeof color);
    tot = 0;
  }
  void Insert(string &s){
    int len = s.length();
    int p = 0;
    for(int i=0;i<len;i++){
      int c = s[i] - 'a';
      if(ch[p][c] == 0){
        ch[p][c] = ++tot;
      }
      p = ch[p][c];
    }
    color[p] += 1;
  }
  int Serach(string &s){
    int len = s.length();
    int p = 0;
    int ans = 0;
    for(int i=0;i<len;i++){
      int x = s[i] - 'a';
      ans += color[p];      
      if(ch[p][x] == 0){
        return ans;
      }
      p = ch[p][x];
    }
    ans += color[p];
    return ans;
  }
};
Trie trie;
int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n, m;
  cin >> n >> m;
  for(int i=0;i<n;i++){
    string s;
    cin >> s;
    trie.Insert(s);
  }
  for(int i=0;i<m;i++){
    string s;
    cin >> s;
    cout << trie.Serach(s) << '\n';
  }
  return 0;
}

The XOR Largest Pair

  • 问你一个数组中任意两个数的异或最大值
  • 这就是 01 t r i e 01trie 01trie树的妙用,常规想法你必须 O ( n 2 ) O(n^2) O(n2),但是从二进制数的角度就可以优化到 O ( n l o g n ) O(nlogn) O(nlogn),把所有数都转化成 01 01 01串插入到trie树中,然后从根节点从高位到低位查询,因为就两种状态,一定是一棵二叉树,所以要么 0 0 0要么 1 1 1,为了让答案更大我们显然应该使得高位异或出来为 1 1 1,按照这个思路可以很容易求解
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

struct Trie{
  int ch[3200000][2];
  int tot;
  Trie(){
    memset(ch, 0, sizeof ch);
    tot = 0;
  }
  inline void Insert(int n){
    int p = 0;
    for(int i=31;i>=0;i--){
      int x = ((n >> i) & 1);
      if(!ch[p][x]){
        ch[p][x] = ++tot;
      }
      p = ch[p][x];
    }
  }
  int Serach(int n){
    int p = 0;
    int ans = 0;
    for(int i=31;i>=0;i--){
      int x = ((n >> i) & 1);
      if(ch[p][x ^ 1] > 0){
        ans |= (1 << i);
        p = ch[p][x ^ 1];
      }else{
        p = ch[p][x];
      }
    }
    return ans;
  }
};
int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int n;
  cin >> n;
  vector<int> a(n);
  Trie trie(n);
  for(int i=0;i<n;i++){
    cin >> a[i];
    trie.Insert(a[i]);
  }
  int ans = 0;
  for(int i=0;i<n;i++){
    ans = max(ans, trie.Serach(a[i]));
  }
  cout << ans;
  return 0;
}

The xor-longest Path

  • 求树上两点异或和的最大值
  • 非常经典的问题,是刚才的问题的变种,刚才那个问题最暴力的方法是两个两个的看,这个问题最暴力的方法也是两个两个的看,但是我们可以求出所有点到根节点之间的距离异或和,因为异或的特殊性质,这个问题就可以看作求所有点到根节点之间的距离异或和中的任意两个数之间的异或最大值,这样就把问题转化回刚才的问题了
#include <bits/stdc++.h>

using namespace std;

const int N = 2e5 + 10;
struct Edge{
    int nxt;
    int to;
    int val;
}edge[N];
int cnt = 0;
int head[N];
void Add_Edge(int u, int v, int w){
    edge[cnt].to = v;
    edge[cnt].nxt = head[u];
    edge[cnt].val = w;
    head[u] = cnt++;
}
int dis[N];
void Dfs(int u, int fa){
    for(int i=head[u];~i;i=edge[i].nxt){
        int v = edge[i].to;
        if(v == fa) continue;
        dis[v] = dis[u] ^ edge[i].val;
        Dfs(v, u);
    }
}
struct Trie{
    int ch[3200000][2];
    int tot;
    Trie(){
        memset(ch, 0, sizeof ch);
        tot = 0;
    }
    void Insert(int n){
        int p = 0;
        for(int i=31;i>=0;i--){
            int x = ((n >> i) & 1);
            if(ch[p][x] == 0){
                ch[p][x] = ++tot;
            }
            p = ch[p][x];
        }
    }
    int Search(int n){
        int p = 0;
        int ans = 0;
        for(int i=31;i>=0;i--){
            int x = ((n >> i) & 1);
            if(ch[p][x ^ 1] > 0){
                ans |= (1 << i);
                p = ch[p][x ^ 1];
            }else{
                p = ch[p][x];
            }
        }
        return ans;
    }
}trie;
int main(){
    int n;
    cin >> n;
    memset(head, -1, sizeof head);
    for(int i=1;i<n;i++){
        int u, v, w;
        cin >> u >> v >> w;
        Add_Edge(u, v, w);
        Add_Edge(v, u, w);
    }
    Dfs(0, 0);
    int ans = 0;
    for(int i=0;i<n;i++){
        trie.Insert(dis[i]);
    }
    for(int i=0;i<n;i++){
        ans = max(ans, trie.Search(dis[i]));
    }
    cout << ans;
    return 0;
}

Xor Sum

  • 给定一个数组 a a a,给出 m m m次询问,每次询问 a a a数组中与给定数组中的异或结果最大的数
  • a a a建立 01 t r i e 01trie 01trie树,每次查询的时候从根节点开始查找,假设当前为 x x x,如果 c h [ p ] [ x ⊕ 1 ch[p][x\oplus1 ch[p][x1]存在说明答案的当前位是 x ⊕ 1 x\oplus1 x1;否则答案的当前位只能是 x x x
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

struct Trie{
  int ch[3200005][2];
  int tot;
  Trie(){
    tot = 0;
  }
  void Insert(ll n){
    int p = 0;
    for(int i=31;i>=0;i--){
      int c = (n >> i & 1);
      if(ch[p][c] == 0){
        ch[p][c] = ++tot;
      }
      p = ch[p][c];
    }
  }
  ll Search(ll n){
    int p = 0;
    ll ans = 0;
    for(int i=31;i>=0;i--){
      int c = (n >> i & 1);
      if(ch[p][c ^ 1] > 0){
        p = ch[p][c ^ 1];
        ans |= ((c ^ 1) << i);
      }else{
        p = ch[p][c];
        ans |= (c << i);
      }
    }
    return ans;
  }
  void Clear(int p){
    if(ch[p][0] > 0) Clear(ch[p][0]);
    if(ch[p][1] > 0) Clear(ch[p][1]);
    ch[p][0] = ch[p][1] = 0;
    tot = 0;
  }
};
Trie trie;
int main(){
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  int t;
  cin >> t;
  for(int kase=1;kase<=t;kase++){
    int n, m;
    cin >> n >> m;
    for(int i=1;i<=n;i++){
      ll u;
      cin >> u;
      trie.Insert(u);
    }
    cout << "Case #" << kase << ":\n";
    for(int i=0;i<m;i++){
      ll s;
      cin >> s;
      cout << trie.Search(s) << '\n';
    }
    trie.Clear(0);
  }
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clarence Liu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值