算法竞赛进阶指南 基本数据结构 0x16 Trie

Trie(字典树)是一种用于实现字符串快速检索多叉树结构。Trie的每个节点都拥有若干个字符指针,若在插入或检索字符串时扫描到一个字符c,就沿着当前节点的c字符指针,走向该指针指向的节点。下面我们来详细讨论Trie的基本操作过程。

初始化:
一颗空Trie仅包含一个根节点,该点的字符指针均指向空。

插入:
当需要插入一个字符串S时,我们令一个指针P起初指向根节点。然后,依次扫描S中的每个字符c。
1.若P的c字符指针指向一个已经存在的节点Q,则令P = Q
2.若P的c字符指针指向空,则新建一个节点Q,令P的c字符指针指向Q,然后令P = Q

当S中的字符扫描完毕时,在当前节点P上标记它是一个字符串的末尾。

检索:
当需要检索一个字符串S在Trie中是否存在时,我们令一个指针P起初指向根节点,然后依次扫描S中的每个字符c:

1.若P的c字符指针指向空,则说明S没有被插入过Trie,结束检索
2.若P的c字符指针指向一个已经存在的节点Q,则令P = Q

当S中的字符扫描完毕时,若当前节点P被标记为一个字符串的末尾,则说明S在Trie中存在,否则说明S没有被插入过Trie

在上图所示的例子中,需要插入和检索的字符串都由小写字母构成,所以Trie的每个节点具有26个字符指针,分别为a到z。可以看出在Trie中,字符数据都体现在树的边(指针)上,树的节点仅保存一些额外信息,例如单词结尾标记等。其空间复杂度时O(NC),其中N是节点个数,C是字符集的大小。

1、AcWing 142. 前缀统计

题意 :

  • 给定 N 个字符串 S1,S2…SN,接下来进行 M 次询问,每次询问给定一个字符串 T,求 S1∼SN 中有多少个字符串是 T 的前缀。
  • 输入字符串的总长度不超过 10^6,仅包含小写字母。

思路 :

  • 不论是插入还是检索,一开始指针p都初始化为0,如果当前这个节点是空的,如果是插入,那么新建这个节点并将指针p指向这个节点,如果是检索,说明没有这个字符串,就break
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e6 + 10;

int n, m;
int son[N][26], idx;
int cnt[N];
char s[N];

void insert() {
    int p = 0;
    for (int i = 0; s[i]; ++ i) {
        int ch = s[i] - 'a';
        if (!son[p][ch]) son[p][ch] = ++ idx;
        p = son[p][ch];
    }
    ++ cnt[p];
}
int search() {
    int p = 0, res = 0;
    for (int i = 0; s[i]; ++ i) {
        int ch = s[i] - 'a';
        if (!son[p][ch]) break;
        p = son[p][ch];
        res += cnt[p];
    }
    return res;
}

int main() {
    scanf("%d%d", &n, &m);
    while (n -- ) {
        scanf("%s", s);
        insert();
    }
    while (m -- ) {
        scanf("%s", s);
        printf("%d\n", search());
    }
}

2、AcWing 143. 最大异或对

题意 :

  • 在给定的 N 个整数 A1,A2……AN 中选出两个进行 xor(异或)运算,得到的结果最大是多少?

思路 :

  • “选出两个”,因此可以用Trie来找在确定某一个(即遍历)的情况下,另一个可以使结果最大的值
  • 一定注意我们是从最高位开始存的!因为是要结果最大,因此位越高,权重越大
#include <iostream>
using namespace std;
const int N = 1e5 + 10, M = 31 * 1e5 + 10;

int n, a[N];
int son[M][2], idx;

void insert(int x) {
    int p = 0;
    for (int i = 30; i >= 0; -- i) {
        int &s = son[p][x >> i & 1];
        if (!s) s = ++ idx;
        p = s;
    }
}
int search(int x) {
    int p = 0, res = 0;
    for (int i = 30; i >= 0; -- i) {
        int now = x >> i & 1;
        if (son[p][!now]) {
            res += 1 << i;
            p = son[p][!now];
        } else p = son[p][now];
    }
    return res;
}

int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; ++ i) {
        scanf("%d", &a[i]);
        insert(a[i]);
    }
    int mx = 0;
    for (int i = 0; i < n; ++ i) mx = max(mx, search(a[i]));
    printf("%d", mx);
}

3、AcWing 144. 最长异或值路径

题意 :

  • 给定一个树,树上的边都具有权值。
  • 树中一条路径的异或长度被定义为路径上所有边的权值的异或和:

思路 :

  • 假设d[i]表示根节点到x的路径上所有边权的xor值
  • 由于 a xor a = 0的性质,我们可以得知树上x到y的路径上所有边权的异或和等于d[x] xor d[y]
  • 因此,问题转化为从d[1] ~ d[n]这n个数中选两个,xor的结果最大,即求解“最大异或对”
  • 注意是h[u] = idx ++ ;!!!而不是++idx
#include <iostream>
#include <cstring>
using namespace std;
const int N = 1e5 + 10, M = 2e5 + 10, K = 3e6 + 10;

int n;
int h[N], c[M], e[M], ne[M], idx;
int d[N];
int son[K][2];

void add(int u, int v, int w) {
    e[idx] = v; c[idx] = w; ne[idx] = h[u]; h[u] = idx ++ ;
}
void dfs(int u, int father, int sum) {
    d[u] = sum;
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j != father) dfs(j, u, sum ^ c[i]);
    }
}
void insert(int x) {
    int p = 0;
    for (int i = 30; i >= 0; -- i) {
        int now = x >> i & 1;
        if (!son[p][now]) son[p][now] = ++ idx;
        p = son[p][now];
    }
}
int search(int x) {
    int p = 0, res = 0;
    for (int i = 30; i >= 0; -- i) {
        int now = x >> i & 1;
        if (son[p][!now]) {
            res += 1 << i;
            p = son[p][!now];
        } else p = son[p][now];
    }
    return res;
}

int main() {
    scanf("%d", &n);
    memset(h, -1, sizeof h);
    for (int i = 0, u, v, w; i < n - 1; ++ i) {
        scanf("%d%d%d", &u, &v, &w);
        add(u, v, w);
        add(v, u, w);
    }
    dfs(0, -1, 0);
    for (int i = 0; i < n; ++ i) insert(d[i]);
    int mx = 0;
    for (int i = 0; i < n; ++ i) mx = max(mx, search(d[i]));
    printf("%d", mx);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值