简介
字典树是一种利用树形结构来存储字符串的数据结构。利用树形结构一对多的特点,每个元素都可以有多个子元素,因此在字符串比较过程中,只需要判断最后一个元素的祖先链是否相等,时间复杂度为
O
(
n
)
O\left ( n \right)
O(n)。
先来看一个字典树的实例图:由于本人太懒,引用图片来源于OI Wiki 字典树 (Trie) (这个图画起来是真的费时间啊!!!)
通过对不同的字符串,可以根据其共有的前缀部分来建立一棵树,例如上图中125就代表字符串
a
a
\;aa\;
aa。
不难得到实现代码如下
int trie[maxn][26]; //列的个数取决于字符串中字符的种类,这里只考虑小写字母
int e[maxn];
int num;
void insert(char* str) //插入字符串
{
int pos = 0; //初始化根的位置
for (int i = 0; str[i]; i++)
{
int leaf = str[i] - 'a';
if (!trie[pos][leaf])
{
trie[pos][leaf] = ++num; //还没有编号的进行编号
}
pos = trie[pos][leaf]; //迭代更新位置
}
e[pos]++;
}
bool find(char* str)
{
int pos = root;
for (int i = 0; str[i]; i++)
{
int leaf = str[i] - 'a';
if (trie[pos][leaf] == 0) //如果该结点标号为0,则一定不存在该字符串
return false;
pos = trie[pos][leaf]; //继续向下查询
}
if (!e[pos])
{
return false;
}
return true;
}
例题:
Codeforces 1285D
题目大意是给出
n
\;n\;
n个数,要求找到一个数
X
\;X\;
X,使得所有
a
i
⊕
X
\;a_i⊕X\;
ai⊕X 中最大的值最小,输出这个异或值。
这是一道经典的
01
T
r
i
e
\;01Trie\;
01Trie,如何来考虑这个问题首先我们最所有的数建立起一个01字典树,也就是将一个数转换为二进制后插入到一个字典树中,接下来来考虑这样一个问题,由于只有0和1两个元素,所以构成的字典树一定是一颗二叉树,那么对应的字典树上每个结点也就只可能包含1或2个结点(非叶结点)。如果只有一个结点,为了要让异或值尽可能的小,所以这一位上的结果一定是为0的,因为只有一个结点,不管是0还是1,只要选和它相同的数就能使异或值为0;而对于两个结点,在这一位上的异或值一定就是1,这是因为两个结点一定是0和1各一个,所以无论这一位选谁异或值一定都是1,然后就需要动态规划去考虑两个结点哪个字数的异或值更小,可以递归实现这个过程。于是解决方法如下:
#include<bits/stdc++.h>
using namespace std;
const int maxn = 5e6 + 5;
int trie[maxn][2];
int n, cnt;
inline int read() //一个无聊的快读过程
{
int t = 0;
char ch = getchar();
while (ch < '0' || ch>'9')
{
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
t = (t << 3) + (t << 1) + (ch ^ 48);
ch = getchar();
}
return t;
}
inline void write(int t) //一个无聊的快写过程
{
if (t > 9)
{
write(t / 10);
}
putchar(t % 10 + '0');
}
inline void insert(int t) //一个无聊的插入过程
{
int pos = 0;
for (int i = 29; i >= 0; i--) //题目中最大取到2^30,所以从高位最大值出依此插入每一位
{
int x = (t >> i) & 1;
if (!trie[pos][x])
{
trie[pos][x] = ++cnt;
}
pos = trie[pos][x];
}
}
inline int solve(int t, int cur) //一个重点的解决过程
{
if (t == -1) //因为建树的30位是从0-29,所以递归边界要到-1
{
return 0;
}
if (!trie[cur][0]) //两种对应的一个结点的情况
{
return solve(t - 1, trie[cur][1]);
}
else if (!trie[cur][1])
{
return solve(t - 1, trie[cur][0]);
}
else
{
//这里是关键部分,如果存在两个结点,首先当前位一定是1,然后再在两个子树中选取较小的一部分
return (1 << t) + min(solve(t - 1, trie[cur][0]), solve(t - 1, trie[cur][1]));
}
}
int main() //一个无聊的主函数
{
n = read();
while (n--)
{
insert(read());
}
write(solve(29, 0));
putchar(10);
return 0;
}
POJ 1204
题目大意为给出一个
L
×
C
L×C
L×C大小的字符矩阵,然后给出多个模式串,要求在矩阵中找出能够匹配每个模式串的首字母的位置,并且字符串的摆放方式有8种,找到匹配的字符串后还要给出字符串的朝向。
分析题目,对于多模式串肯定是要建立字典树来查询,所以可以将所有模式串建立字典树,然后枚举矩阵中所有字符的每个方向,匹配到返回坐标信息和方向。
Solution:
#include<iostream>
using namespace std;
#include<cstdio>
#include<cstdlib>
#include<cstring>
const int maxn = 10010;
int dir[][2] = { -1,0,-1,1,0,1,1,1,1,0,1,-1,0,-1,-1,-1 }; //用于表示八个方向
char s[1005][1005]; //表示字符矩阵
char str[1005]; //待查询字符串,即模式串
int n, m, T;
int cnt;
struct trie
{
int c[30];
int s; //标记是第几个模式串
}t[maxn * 50];
struct answer //用于记录答案信息,即坐标和方向
{
int x, y, z;
}ans[1005];
void insert(int num)
{
int x = 0;
int len = strlen(str);
for (int i = 0; i < len; i++)
{
int y = str[i] - 'A';
if (!t[x].c[y])
{
cnt++;
t[x].c[y] = cnt;
}
x = t[x].c[y];
}
t[x].s = num;
return;
}
bool check(int x, int y) //判断是否还在矩阵内部
{
if (x >= 0 && x < n && y >= 0 && y < m)
return true;
return false;
}
void query(int x, int y, int z)
{
int pos_x = x, pos_y = y;
int cur = 0;
while (check(x, y))
{
int b = (s[x][y] - 'A');
if (!t[cur].c[b])
return;
cur = t[cur].c[b];
if (t[cur].s)
{
int num = t[cur].s;
ans[num].x = pos_x;
ans[num].y = pos_y;
ans[num].z = z;
}
x = x + dir[z][0];
y = y + dir[z][1];
}
return;
}
void solve()
{
for (int i = 0; i < n; i++)
{
for (int j = 0; j < m; j++)
{
for (int k = 0; k < 8; k++)
{
query(i, j, k); //枚举查询每一个字符的八个方向构成的字符串是否符合要求
}
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin >> n >> m >> T;
for (int i = 0; i < n; i++)
{
cin >> s[i];
}
for (int i = 1; i <= T; i++)
{
cin >> str;
insert(i);
}
solve();
for (int i = 1; i <= T; i++)
{
cout << ans[i].x << ' ' << ans[i].y << ' ' << (char)(ans[i].z + 'A') << endl;
}
return 0;
}
完结撒花!!!