数据结构 —— Trie 树

一个笔记需要一张头图:

Trie 树是一种维护(广义)字符串(我们认为广义字符串是一切可以被线性描述的类型,例如,我们认为整数(无论是哪种进制下)是一种广义字符串;有理数也是一种广义字符串(使用无限循环小数方式表述,可能需要一些特殊处理。))的数据结构,其特征为适于处理前缀类型或寻找类型(i.e. 以遍历每一位的方式找到一个符合的广义字符串)的问题。

下文我们使用⌈字符串⌋来表示广义字符串,特别的,也表示(在上下文中推断出的)正在使用 Trie 树维护的类型。

一个典型的 Trie 树如下图所示:(图源 OI-wiki)

trie1

从图中可以看出一部分 Trie 树的写法:多半需要一个数组来维护节点及其儿子节点,也就是说,一棵很普通的树的写法。

那么容易写出结构体定义:

struct Trie {
	int tr[100010][20], end[100010], root, cnt;
	Trie() {root = 1;cnt = 1;memset(tr, 0, sizeof(tr));memset(end, 0, sizeof(end));}
};

可以看到在这里我们定义了end数组,其意义为在该节点上结束的字符串数量。

插入是容易实现的(这里认为维护的是数字串):

void insert(string s) {
    int u = root;
    for (int i = 0; i <= s.size() - 1; i++) {
        if (!tr[u][s[i] - '0']) {
            tr[u][s[i] - '0'] = ++cnt;
        }
        u = tr[u][s[i] - '0'];
    }
    end[u]++;
}

查询函数通常与题目非常相关,但是我们可以提炼出一个大致的模型(这里和上面做出相同的假设,i.e.,维护的是数字串):

type query(string s) {
    int u = root;
    type ans;
    rep (i, 0, s.size() - 1) {
        u = tr[u][s[i] - '0'];
        doSomethingWithAns(ans, u);
    }
    return ans;
}

顺理成章的,接下来是一些例题…… 不过我们需要将问题分类。

前缀类问题

前缀类问题,顾名思义,即查询有关于某个字符串前缀的问题。

注意到 Trie 树非常适合做这件事,原因是 Trie 树的结构使得查询某个字符串的前缀时会经过所有维护其前缀的节点(这些节点在插入这个字符串时被创建)。

给出一道非常经典的例题。

Phone List

一句话题意:给定 \(n\) 个数字串,询问是否存在两个字符串 \(a, b\) 使得 \(a\)\(b\) 的前缀。

对这\(n\)个串建立 Trie 即可。

代码:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
#ifndef ONLINE_JUDGE
#include "../../../debug.hpp"
#else
#define debug(...)
#define debugArr(...)
#endif
template <typename T>
using V = vector<T>;
#define rep(i, x, y) for (int i = x; i <= y; i++)
#define rept(type, i, x, y) for (type i = x; i <= y; i++)
#define all(v) v.begin(), v.end()
#define range(v) 0, v.size() - 1 // 0 - indexed
#define sz(v) v.size()
using ll = long long;

struct Trie {
	int tr[100010][20], end[100010], root, cnt;
	Trie() {root = 1;cnt = 1;memset(tr, 0, sizeof(tr));memset(end, 0, sizeof(end));}
	void insert(string s) {
		int u = root;
		rep (i, 0, s.size() - 1) {
			if (!tr[u][s[i] - '0']) {
				tr[u][s[i] - '0'] = ++cnt;
			}
			u = tr[u][s[i] - '0'];
		}
		debug(s, u);
		end[u]++;
	}
	bool query(string s) {
		int u = root;
		bool flag = false;
		rep (i, 0, s.size() - 1) {
			u = tr[u][s[i] - '0'];
		}
		debug(s, u);
		for (int i = 0; i < 10; i++) {
			if (tr[u][i]) flag = true;
		}
		return (flag || end[u] > 1);
	}
};

int t, n;
string s[10010];

int main()
{
	// cout << "is running" << "\n";
	cin >> t;
	while (t--) {
		cin >> n;
		rep (i, 1, n) {
			cin >> s[i];
		}
		Trie tree;
		rep (i, 1, n) {
			tree.insert(s[i]);
		}
		bool ans = false;
		rep (i, 1, n) {
			ans |= tree.query(s[i]);
			debug(ans);
		}
		cout << (ans ? "NO" : "YES") << "\n";
	}
}

寻找类

这一类问题通常的需求是寻找一个符合某种性质的字符串。

同样给出一个典型例题。

The XOR Largest Pair

一句话题意:从\(a_1,a_2,\dots,a_n\)中寻找两个数使其异或值最大。

显然先枚举一个,随后需要处理⌈异或值最大⌋这个问题。

把数字放在二进制下考虑是合理的,随后发现一个简单的性质:如果 \(a_i > b_i\)\(a_j \geq b_j\) 对所有的 \(j\) 成立(此处 \(a,b\) 为正整数,\(a_i\)代表二进制下 \(a\) 的最高第 \(i\) 位),那么有 \(a > b\)

回到这道题,我们发现对于 \(a_i\) 如果可以选出一个数 \(a_j\),使得 \(a_i \oplus a_j\) 的最高位为 \(1\),那 \(a_j\) 一定比使异或值最高位为 \(0\) 的数优。这使我们思考按位由高到低贪心。恰好,Trie 树适于处理这种情况。

接下来的实现是简单的,给出代码:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
#ifndef ONLINE_JUDGE
#include "../../../debug.hpp"
#else
#define debug(...)
#define debugArr(...)
#endif
template <typename T>
using V = vector<T>;
#define rep(i, x, y) for (int i = x; i <= y; i++)
#define per(i, x, y) for (int i = x; i >= y; i--)
#define rept(type, i, x, y) for (type i = x; i <= y; i++)
#define all(v) v.begin(), v.end()
#define range(v) 0, v.size() - 1 // 0 - indexed
#define sz(v) v.size()
using ll = long long;
int tr[3000010][20], root = 1, cnt = 1;
void insert(int s) {
	int u = root;
	per (i, 32, 1) {
		int v = (s & (1 << (i - 1))) >> (i - 1);
		if (!tr[u][v]) {
			tr[u][v] = ++cnt;
		}
		debug(s, u, tr[u][v]);
		u = tr[u][v];
	}
	debug(s, u);
}
int query(int s) {
	int u = root, ans = 0;
	per (i, 32, 1) {
		int v = ((s & (1 << (i - 1))) >> (i - 1));
		debug(s, v, u);
		if (tr[u][v ^ 1]) {
			u = tr[u][v ^ 1];
			ans <<= 1;
			ans += v ^ 1;
		} else {
			u = tr[u][v];
			ans <<= 1;
			ans += v;
		}
	}
	debug(s, ans);
	return ans;
}

int a[100010], n;

int main()
{
	cin >> n;
	rep (i, 1, n) {
		cin >> a[i];
		insert(a[i]);
	}
	int ans = 0;
	rep (i, 1, n) {
		int res = query(a[i]);
		ans = max(ans, res ^ a[i]);
	}
	cout << ans << "\n";
}
posted @ 2024-07-01 18:13  IANYEYZ  阅读(21)  评论(1编辑  收藏  举报