字典树
字典树
算法思想
字典树()是一个比较简单的数据结构,也叫前缀树或 树,用来存储和查询字符串。
例如,water
,wish
,win
,tie
,tired
这几个单词可以用以下方式存储 :
此时每一个叶子结点递归往上到根节点都是对应一个字符串。
其中每个字符占据一个节点,拥有相同前缀的字符串可以共用部分节点。
起始点是特殊点(我们设为 号点),不存储字符。
建树的代码如下:
const int MAXN = 500005;
int nxt[MAXN][26], cnt; // nxt[i][c] 表示 i 号点所连、存储字符为 c + 'a' 的点的编号
void init() // 初始化
{
memset(nxt, 0, sizeof(nxt));
cnt = 1;
}
void insert(const string &s) // 插入字符串
{
int cur = 1;
for (auto c : s)
{
// 尽可能重用之前的路径,如果做不到则新建节点
if (!nxt[cur][c - 'a'])
nxt[cur][c - 'a'] = ++cnt;
cur = nxt[cur][c - 'a']; // 继续向下
}
}
查询代码如下 :
bool Find(const string &s) // 查找某个前缀是否出现过
{
int cur = 1;
for (auto c : s)
{
// 沿着前缀所决定的路径往下走,如果中途发现某个节点不存在,说明前缀不存在
if (!nxt[cur][c - 'a'])
return false;
cur = nxt[cur][c - 'a'];
}
return true;
}
如果是查询某个字符串是否存在,可以另开一个 数组,在插入完成时,把 设置为 ,然后先按查询前缀的方法查询,在结尾处再判断一下 的值。
这是一种常见的套路,即用叶子节点代表整个字符串,保存某些信息。
字典树是一种比较典型的空间换时间的数据结构,我们牺牲了字符串个数 字符串平均字符数 字符集大小的空间,但可以用 的时间查询,其中 为查询的前缀或字符串的长度。
代码实现
给出 个字符串和 个查询,每次查询给出一个字符串 。如果 没有被给出,输出
WRONG
,如果 被给出并且是第一次被查询,输出OK
,如果 被给出并且不是第一次被查询,输出REPEAT
。
解题思路
维护每一个字符串出现的次数即可,记录在叶子结点上。
AC CODE
#include <bits/stdc++.h>
using namespace std;
const int _ = 3e5 + 5;
int n, m, cnt;
int nxt[_][26];
bool vis[_];
char s[60];
void insert(string s)
{
int t = 0, len = s.size();
for (int i = 0; i < len; i++)
{
int c = s[i] - 'a';
if (!nxt[t][c])
nxt[t][c] = ++cnt;
t = nxt[t][c];
}
}
int search(string s)
{
int t = 0, len = s.size();
for (int i = 0; i < len; i++)
{
int c = s[i] - 'a';
if (!nxt[t][c])
return 0;
t = nxt[t][c];
}
if (!vis[t])
{
vis[t] = true;
return 1;
}
return 2;
}
signed main()
{
int res;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%s", s);
insert(s);
}
scanf("%d", &m);
for (int i = 1; i <= m; i++)
{
scanf("%s", s);
res = search(s);
if (!res)
puts("WRONG");
else if (res == 1)
puts("OK");
else
puts("REPEAT");
}
return 0;
}
扩展
在给定的 个整数 中选出两个进行异或运算,得到的结果最大是多少?
解题思路
先引入数据结构, 是 树衍生出的一种数据结构,它可以用来维护与 相关的题目。
十分简单,我们把若干个数转换成二进制表示,也就是若干个 串,然后把这些 串插入 树,得到的 树称之为 。
异或 :将两个数都变成二进制,只有在两个比较的位不同时其结果是 ,否则结果为 。
(x >> i) & 1
判断十进制数 变成二进制后的第 位是多少。
我们可以发现,想令异或和最大,那么我们应该尽量保证高位的数不相同。
因为假设我们令从左往右数第 位不同,但我们不取第 位,那么即使后面所有位上的数字都不相同,损失最多为 。
如果我们把第 位取相反的数,后面可能的贡献总和也一定比不取第 位的损失要小。
于是我们可以想出一个贪心策略。对于给定的数 ,我们可以二进制从高位到低位遍历。
假设当前遍历到了从左往右的第 位,若深度为 的结点中存在权值与 的第 位相反的结点,我们递归进入该结点;
反之进入另外一个结点递归。
AC CODE
#include <bits/stdc++.h>
using namespace std;
int a[3500005], tr[3500005][2], n, cnt = 1, ans;
inline int read()
{
static char ch;
static int n;
n = 0;
while (ch < '0' || ch > '9')
ch = getchar();
while (ch >= '0' && ch <= '9')
n = n * 10 + ch - '0', ch = getchar();
return n;
}
inline void insert(int v)
{
int u = 1;
for (int i = 31; i >= 0; --i)
{
int k = (v >> i) & 1;
if (!tr[u][k])
tr[u][k] = ++cnt;
u = tr[u][k];
}
}
inline int query(int x)
{
int u = 1, ans = 0;
for (int i = 31; i >= 0; --i)
{
int c = (x >> i) & 1;
if (tr[u][c ^ 1])
{
u = tr[u][c ^ 1];
ans += (int)(1 << i);
}
else
u = tr[u][c];
}
return ans;
}
signed main()
{
n = read();
for (int i = 1; i <= n; ++i)
{
a[i] = read();
insert(a[i]);
}
for(int i = 1; i <= n; ++i)
ans = max(ans, query(a[i]));
printf("%d", ans);
return 0;
}
本文来自博客园,作者:蒟蒻orz,转载请注明原文链接:https://www.cnblogs.com/orzz/p/18122129
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话