Trie树入门

孔子有言“温故而知新”,由于近期复习了 trie 树的基本知识点,来写篇对他的总结。

处理的问题

trie 树经常用于处理一类字符串问题,比如他可以查询当前字符串是否在字典中出现过、可以得知每个字符串有多少个前缀或者后缀、是 AC 自动机的一部分等等。待会后面还会讲到一个 01trie ,他就可以用来处理整数位运算类的问题。

其实 trie 树不像线段树,虽然说他们处理的都是同一类问题,但是 trie 树实际上比线段树好实现,而且变化没有太大,固定的板子是不变的,只是在上面用其它数组简单维护一些东西,不得不说 trie 树只是一种在处理问题过程中的一小步吧。不过有时候正是因为有了 trie 树才能完成问题的转化,从而简化问题。

步骤

大体的板子的步骤其实非常简单,在这就提一嘴罢:

  1. 初始化 trie 树(谨防多侧),把表示节点个数的 tot 和表示边的 t 数组全部清零,我们可以认为 0 号节点存在,是第一个节点,有他指向字符串里的第一个字符。
  2. Insert 函数,进行插入,从根节点开始访问,按照当前字符串里的字符作为边的值指向子节点,如果没有,则动态新建一个
  3. Query 函数,进行查询,总体的遍历方式和插入一样,只是过程中不要新建节点,而且要在函数中维护要查询的东西即可。

再单独列出 trie 树的注意点:

  • 根节点是一个空节点

    一定要给根节点留出位置

  • 采取动态开点方式

    如果当前节点已经有当前串下一个位置的字符这个儿子,那么就不用建立,否则要建立一个新的儿子。

可以借助此图理解理解(一张高清大图):

01trie

01trie 是一种可以处理整数位运算的 trie 树。 01trie 本质和 trie 没区别,因为他和 trie 树一样很简单。他就是把整数变成二进制串(不足位补 0),然后再按 trie 树有水有老的套路去处理。其所维护者与 trie 大同小异,但是 01trie 很多时候会有一个优先级:比如说遍历 0 的边和遍历 1 的边那个能让得到的值更怎么样,那么会优先遍历当前最优的边,之所以这样做是因为我们知道不管是二进制也好,十进制也好, n 进制也好,他们都是优先满足高位的。

例题

选了 2 题例题。

T1

一本通1478:The xor-longest Path

题意: 给定一棵 n 个点的带权树,求树上最长的异或和路径。

题解: 随便指定一个 root ,然后算出从 root 出发到所有节点的异或和。我们再设 T(u,v)uv 路径上的异或和,那么可得:T(u,v)=T(root,u)T(root,v), 因为从他们的最近公共祖先到根的异或和抵消了。

之后我们利用 01trie 水过即可。

这道题本质是一个性质及 01trie 导致模型的转换,使得问题变简单。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define L(i, a, b) for(int i = a; i <= b; i++)
#define R(i, a, b) for(int i = a; i >= b; i--)
using namespace std;
const int N = 1e6 + 10;
struct A{
    int v, w;
};
int n, val[N], mx;
vector<A> g[N];
struct Trie{
    int t[N * 2][2], tot;
    void Init(){
        tot = 0;
        memset(t, 0, sizeof(t));
    }
    void Insert(int x){
        int p = 0;
        R(i, 30, 0){
            int now = (bool)(x & (1 << i));
            if(!t[p][now])
                t[p][now] = ++tot;
            p = t[p][now];
        }
    }
    int Query(int x){
        int p = 0, sum = 0;
        R(i, 30, 0){
            int now = (bool)(x & (1 << i));
            if(t[p][now ^ 1])
                p = t[p][now ^ 1], sum += 1 << i;
            else
                p = t[p][now];
        }
        return sum;
    }
} t;
void dfs(int x, int p){
    t.Insert(val[x]);
    mx = max(mx, t.Query(val[x]));
    for(A e: g[x]){
        int v = e.v, w = e.w;
        if(v == p) continue;
        val[v] = w ^ val[x];
        dfs(v, x);
    }
}
int main(){
    while(~scanf("%d", &n)){
        mx = 0, t.Init();
        L(i, 1, n) g[i].clear();
        L(i, 1, n - 1){
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            g[u].push_back((A){v, w});
            g[v].push_back((A){u, w});
        }
        val[1] = 0;
        dfs(1, 0);
        printf("%d\n", mx);
    }
    return 0;
}

T2

POJ2513——道巨水的题。 这里之所以要拿出来,是因为它是通过trie树进行模型转换的一个代表。 那我们

题意:给你一些木棍,木棍首尾分别各有一个单词,请将所有木棍排成一行,使得任意相邻的木棍相接处的单词一样。

题解:我们把单词插入字典树中变成数字编号,不难发现木棍变成了边,数字编号是点的编号,因为是一条而不是一个环,我们只要求是否存在欧拉通路即可。

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#define L(i, a, b) for(int i = a; i <= b; i++)
#define R(i, a, b) for(int i = a; i >= b; i--)
using namespace std;
const int N = 500010;
int n, m, d[N];
pair<int, int> e[N];
struct Trie{
    int tot, c[N], trie[N][26];
    int Insert(char *s){
        int p = 0, l = strlen(s);
        if(s[l - 1] == '\n') l--;
        L(i, 0, l - 1){
            int v = s[i] - '0';
            if(!trie[p][v]) trie[p][v] = ++tot;
            p = trie[p][v];
        }
        if(!c[p]) c[p] = ++n;
        return c[p];
    }
} t;
struct DSU{
    int fa[N];
    void Init(){
        L(i, 1, n) fa[i] = i;
    }
    int Find(int x){
        return x == fa[x]? x : fa[x] = Find(fa[x]);
    }
    void Union(int x, int y){
        x = Find(x), y = Find(y);
        fa[x] = y;
    } 
} dsu;
int main(){
    char s[23], a[12], b[12];
    while(fgets(s, 23, stdin) != NULL){
        sscanf(s, "%s %s", a, b);
        e[++m] = make_pair(t.Insert(a), t.Insert(b));
    }
    dsu.Init();
    L(i, 1, m){
        dsu.Union(e[i].first, e[i].second);
        d[e[i].first]++, d[e[i].second]++;
    }
    int cnt = 0;
    L(i, 1, n){
        if(dsu.Find(i) != dsu.Find(1)){
            puts("Impossible");
            return 0;
        }
        if(d[i] & 1) cnt++;
    }
    if(!cnt || cnt == 2){
        puts("Possible");
    }
    else{
        puts("Impossible");
    }
    return 0;
}
posted @   徐子洋  阅读(6)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示