字典树(Trie)
字典树(Trie)
Trie 是一种能够快速插入和查询字符串的多叉树结构。
节点的编号各不相同,根节点编号为 \(0\) ,其他节点用来标识路径。
还可以标记单词插入的次数。边表示字符。
一般情况下,Trie维护字符串的集合,支持以下两种操作:
1.向集合中插入一个字符串。
2.向集合中查询一个字符串。
建字典树
子数组 ch[p][j]
表示从节点 \(p\) 沿着 \(j\) 这条边走的到子节点。
计数数组 cnt[p]
表示以节点 \(p\) 结尾得到单词的插入次数。
节点编号 idx
用来给节点编号。
1.Trie中只有一个根节点,编号为0。
2.从根开始插入,枚举字符串的所有字符。
如果有儿子,那么直接走到儿子。
反之,先创建儿子,再走到儿子。
3.维护计数数组。
int ch[N][26],cnt[N],idx;
void insert(string s){
int p=0;
for(auto c:s){
int j=c-'a';
if(!ch[p][j]) ch[p][j]=++idx;
p=ch[p][j];
}
cnt[p]++;
}
int query(string s){
int p=0;
for(auto c:s){
int j=c-'a';
if(!ch[p][j]) return 0;
p=ch[p][j];
}
return cnt[p];
}
0-1 Trie 解决异或和相关问题
将整数转化成2进制后,将其存入Trie中。利用贪心思想即可求出最大异或和。
int ch[N<<5][2],idx;
int sz[N<<5];
void ins(int x){
for(int i=31,p=0;i>=0;i--){
int j=x>>i&1;
if(!ch[p][j]) ch[p][j]=++idx;
p=ch[p][j];
sz[p]++;
}
}
void del(int x){
for(int i=31,p=0;i>=0;i--){
int j=x>>i&1;
p=ch[p][j];
--sz[p];
}
}
int query(int x){
int ret=0;
for(int i=31,p=0;i>=0;i--){
int j=((x>>i)&1);
if(ch[p][j^1]&&sz[ch[p][j^1]]){
p=ch[p][j^1];
ret|=(1LL<<i);
}else p=ch[p][j];
}
return ret;
}
这个板子可以解决求出 \(x\) 与集合中的元素选出一个最大异或值。如果想要拿出集合中的元素,只需要 \(query(x)\oplus x\) 即可。
例题: HDU4825]Xor Sum - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
并且多维护了一个 \(sz\) 数组,支持删除集合中的元素。
例题:HDU5536]Chip Factory - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
还有一类经典的问题--最大区间异或。
给你一个数组求出最大区间异或。根据异或的性质,我们可以维护出前缀异或数组。
那么 \(ans_{l,r}=pre_{l-1} \oplus pre_r\) 。那么,我们只需要把前缀异或数组插入进Trie中。问题就变成了基本的
最大异或和 问题。
对于 最大不相交区间异或和 ,我们可以再多维护一个后缀异或和数组。多处理一次即可。记得清空Trie树。
例题: Codechef REBXOR]Nikitosh and xor - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
经典问题--最长异或路径
求 \(u\) 到 \(v\) 的路径时,我们会维护每个节点到根节点的距离 \(d_u\)。
那么 \(dis_{u,v}=d_u+d_v-2\times d_{LCA_{U,V}}\)
根据异或的性质,多的那部分刚好相互抵消。
因此有 \(xor_{u,v}=d_u \oplus d_v\)
所以我们只要维护一下每个节点到根节点的异或和,剩下的步骤就和最大异或值处理一样。