Trie树学习总结

字典树,又称前缀树,是用于快速处理字符串的问题,能做到快速查找到一些字符串上的信息。

另外,Trie树在实现高效的同时,会损耗更多的空间,所以Trie是一种以空间换时间的算法。

Trie的思想

Trie的思想十分简单,其实我在很早之前就已经懂了Trie的思想,只不过一直没有实现,所以就……

咕咕,没事,Trie无论是思想还是代码思想都还是很简单。现在我重新再搞回Trie也很快就代码实现了。

Trie是思想其实用图更容易展现。下面放一张图给大家看看,相信大家很快就会明白了。

如果我们插入字符串:

zlh
AK
tql
AKIOI
AKnoip



上面就是一颗我们建的Trie树。

如果我们要查找“zlh”,那么就会沿着1->2->3点找下去。

如果我们要查找“AKIOI”,那么我们就会沿着4->5->9->10->11点找

也可以十分容易发现"AK"是"AKIOI"的前缀。

那么大家现在应该就能明白Trie的构造了吧。

简单说就是顺着之前Trie树的构造去插入点,例如我们最后插入"AKnoip",就会先走过"A"和"K",然后发现没有匹配的字符了,就先后新建了'n','o','i','p',这几个点。复杂度O(len(字符串))。

查询就会沿着Trie的构造一个一个跳,所以查询复杂度是O(len(字符串))。

那么Trie的思想就是这样了。

Trie的实现

明白了思想,实现就应该很简单了吧,这里通过一道例题来实现Trie。

题目:P2580 于是他错误的点名开始了

定义

struct kkk{
int son[27],cnt;
bool flag;
}trie[maxn];

首先是插入操作:

void insert(string s){
int len=s.size(),u=0; //获取长度len,u是当前点的编号,根的编号是0
for(int i=0;i<len;i++){
int v=s[i]-'a'; //获取位置
if(!trie[u].son[v]) //如果没有继续下去的节点
trie[u].son[v]=++num; //就新建一个
u=trie[u].son[v]; //跳到下一个点去插入
}
trie[u].flag=true; //标记此处是一个单词
}

然后是查询操作:

int find(string s){
int len=s.size(),u=0;
for(int i=0;i<len;i++){
int v=s[i]-'a'; //同上
if(!trie[u].son[v])return 3; //找不到返回没有
u=trie[u].son[v]; //同上
}
if(trie[u].flag==false)return 3; //不是一个单词就返回没有
if(!trie[u].cnt){ //如果没有被点过
trie[u].cnt++; //标记被点过了
return 1; //返回有
} return 2; //不然返回重复
}

那么Trie的实现就这样了。

Trie的考点

  1. 异或XOR以及利用XOR的性质
  2. 后缀转前缀
  3. 记忆化,缩点,重构树,Trie的思想(见 P3294 [SCOI2016]背单词,P2292 [HNOI2004]L语言
  4. 可持久化(详见下文

随便给点题

phone list

The XOR Largest Pair

Nikitosh 和异或

Immediate Decodability

[SCOI2016]背单词

[HNOI2004]L语言

[USACO08DEC]秘密消息Secret Message

最长异或路径

做完上面那几道应该就能了解Trie

习题讲解

1、phone list

题目:Phone list

题目意思十分明确,求有没有一个字符串是其他字符串的前缀,如果有就返回"NO",没有返回"YES"。

这题和前面讲的Trie的作用一模一样,可以快速判断一个字符串是不是已插入的字符串中的前缀。

那么这道题就很简单了,我们先将全部字符串都插入到Trie中。

然后查找每一个字符串,如果字符串下有儿子,那么就代表当前字符串是某个字符串的前缀,直接返回。

代码实现也很简单,可以算是Trie的模板题。

#include<bits/stdc++.h>
using namespace std;
struct kkk{
int son[11],sum;
bool flag;
void clear(){memset(son,0,sizeof(son));sum=0;flag=0;} //清0
}trie[400001];
int n,T,num;
string S[400001];
void insert(string s){
int len=s.size(),u=0;
for(int i=0;i<len;i++){
int v=s[i]-'0';
if(!trie[u].son[v])
trie[u].son[v]=++num;
trie[u].sum++; //表示u有多少个儿子
u=trie[u].son[v];
}
trie[u].flag=true;
}
int find(string s){
int len=s.size(),u=0;
for(int i=0;i<len;i++){
int v=s[i]-'0';
if(!trie[u].son[v])return 0; //找不到字符串就返回
u=trie[u].son[v];
}
if(trie[u].sum==0)return 0; //如果没有儿子就代表不是前缀
return 1; //否则就是前缀
}
int main(){
scanf("%d",&T);
while(T--){
num=0; //记得清0
for(int i=0;i<=400000;i++)trie[i].clear(); //记得清0
scanf("%d",&n);
for(int i=1;i<=n;i++){
cin>>S[i];
insert(S[i]); //一个一个插入
}
bool ans=false;
for(int i=1;i<=n;i++){
int res=find(S[i]); //查询
if(res==1){ans=true;break;} //判断是不是前缀
}
if(ans==true)printf("NO\n"); //输出
else printf("YES\n");
}
}

2、Immediate Decodability

题目:Immediate Decodability

这道题和上面那道题一模一样,不过输入比较恶心,而且数据范围也比较小。

这里的输入就是不断输入字符串S,然后将字符串S插入到Trie中,直到S='9'为止。

到了S='9',就将之前的S都像上面的一样查询一边,输出答案,然后清0。

代码:

#include<bits/stdc++.h>
using namespace std;
struct kkk{
int son[11],sum;
bool flag;
void clear(){memset(son,0,sizeof(son));sum=0;flag=0;}
}trie[2001];
int n,T,num,t;
string S[400001];
void insert(string s){
int len=s.size(),u=0;
for(int i=0;i<len;i++){
int v=s[i]-'0';
if(!trie[u].son[v])
trie[u].son[v]=++num;
trie[u].sum++;
u=trie[u].son[v];
}
trie[u].flag=true;
}
int find(string s){
int len=s.size(),u=0;
for(int i=0;i<len;i++){
int v=s[i]-'0';
if(!trie[u].son[v])return 0;
u=trie[u].son[v];
}
if(trie[u].sum==0)return 0;
return 1;
}
void solve(){ //解决之前的字符串
bool ans=false;
for(int i=1;i<=n;i++){
int res=find(S[i]);
if(res==1){ans=true;break;}
}
if(ans==true)printf("Set %d is not immediately decodable\n",t);
else printf("Set %d is immediately decodable\n",t);
}
int main(){n=1;
while(cin>>S[n]){
if(S[n]=="9"){
t++;
solve(); //解决之前的问题
memset(trie,0,sizeof(trie)); //清0
num=0;n=1;continue; //清0
}
insert(S[n]);n++; //如果不是‘9’就插入
}
}

3、The XOR Largest Pair

题目:The XOR Largest Pair

这道题是一道变向的Trie,也是Trie的一个基本应用。

思路:

1、首先将a[i]转换为二进制的字符串存起来,记得空位要补0

2、然后将这些字符串都插入到Trie里。

3、查询每一个字符串,找到以每一个数和另外其他数的最大异或和。

那么问题就是怎么找一个数和另外其他数的最大异或和了。

我们知道,异或是不同就为1,相同就为0,所以我们因尽量让高位的二进制为1。

所以我们查询的时候就倒着查,也就是如果s[i]为0,那么我们就查1。但是没有1怎么办,我们总不能就此放弃吧,于是我们继续查0。

最后取个最大值就OK了。

看看代码:

#include<bits/stdc++.h>
using namespace std;
struct kkk{
int son[3],sum;
bool flag;
void clear(){memset(son,0,sizeof(son));sum=0;flag=0;}
}trie[4000001];
int n,T,num,ans;
int x[1000001];
void insert(string s){ //插入
int len=s.size(),u=0;
for(int i=0;i<len;i++){
int v=s[i]-'0';
if(!trie[u].son[v])
trie[u].son[v]=++num;
trie[u].sum++;
u=trie[u].son[v];
}
trie[u].flag=true;
}
int find(string s){
int len=s.size(),u=0,ans=0,num=(int)(2147483648ll/2ll);
for(int i=0;i<len;i++){
int v=((s[i]-'0')^1); //倒着查
if(!trie[u].son[v])v^=1; //找不到就倒回来
else ans+=num; //不然就累加进去
u=trie[u].son[v];num/=2; //跳下一个
}
return ans;
}
string decompose(int x){ //拆分成二进制
string s="";
while(x!=0)
s+=(x%2)+'0',x>>=1;
while(s.size()!=31)s+='0';
reverse(s.begin(),s.end());
return s; //返回字符串
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&x[i]);
string s=decompose(x[i]);
insert(s);
}
for(int i=1;i<=n;i++){
int res=find(decompose(x[i]));
ans=max(ans,res); //取最大值
}
printf("%d\n",ans);
}

4、[USACO08DEC]秘密消息Secret Message

题目大意:给你n个信息和m个密码,要你求出对于每一个密码都多少信息,使这些信息的前缀和密码的前缀相同。

理解清题目,这道题就很简单了,我们用信息来在建立Trie时多维护一个数据sum,存的是有多少个信息经过这个节点。那么我们在查询时,先统计有多少信息是密码的前缀再加上当前节点的sum就是答案。

#include<bits/stdc++.h>
using namespace std;
struct kkk{
int son[11],sum,flag;
void clear(){memset(son,0,sizeof(son));sum=0;flag=0;}
}trie[400001];
int n,T,num,ans;
string S[400001];
void insert(string s){
int len=s.size(),u=0;
for(int i=0;i<len;i++){
int v=s[i]-'0';
if(!trie[u].son[v])
trie[u].son[v]=++num;
u=trie[u].son[v];trie[u].sum++; //记录sum
}
trie[u].flag++; //有重复所以要加加
}
int find(string s){
int len=s.size(),u=0,ans=0;
for(int i=0;i<len;i++){
int v=s[i]-'0';
if(!trie[u].son[v])return ans; //找不到
u=trie[u].son[v];
ans+=trie[u].flag; //记录信息是密码前缀
}
return ans-trie[u].flag+trie[u].sum; //-trie[u].flag是为了去重
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++){
int x;
scanf("%d",&x);string s="";char c;
for(int j=1;j<=x;j++)cin>>c,s+=c;
insert(s);
}
for(int i=1;i<=m;i++){
int x;
scanf("%d",&x);string s="";char c;
for(int j=1;j<=x;j++)cin>>c,s+=c;
ans=find(s);
printf("%d\n",ans);
}
}

5、Nikitosh 和异或

题目在这里:「一本通 2.3 例 3」Nikitosh 和异或

首先对于如何求异或最大值我们已经在上文学到了,现在我们要求的是两个没有覆盖的区间异或的和。

异或有一个性质:就是据有可减性,也就是说我们记前缀异或sum数组,如果求[l,r]的异或和,那么答案就是sum[r]^sum[l-1];后缀异或也是一样的

所以我们可以对于每一个位置i都求一遍以i为结尾的区间中,最大的区间异或值。方法是将i前面的每一个前缀sum数组都插入到Trie中,然后find(sum[i]),答案计为l数组

另外,对于每一个位置i都求一遍以i为开头的区间中,最大的区间异或值。方法是将i后面的每一个后缀sum数组都插入到Trie中,然后find(sum[i]),答案计为r数组

那么对于每一个位置i,都统计前面的最大的l,记录下来。表示的是i前面的最大区间异或和

对于每一个位置i,都统计后面的最大的r,记录下来。表示的是i后面的最大区间异或和

那么答案就是对于每一个位置i的 前面的最大区间异或和 和 后面的最大区间异或和 的 和 的最大值

代码:

#include<bits/stdc++.h>
using namespace std;
struct kkk{
int son[3];
void clear(){memset(son,0,sizeof(son));}
}trie[8000001];
int n,T,num,ans;
int x[1000001],l[1000001],r[1000001],s[35];
void insert(int x){
int j=-1,u=0;
memset(s,0,sizeof(s));
while(x){
s[++j]=x&1;
x>>=1;
}
for(int i=31;i>=0;i--){
if(!trie[u].son[s[i]])
trie[u].son[s[i]]=++num;
u=trie[u].son[s[i]];
}
}
int find(int x){
int ans=0,u=0;
for(int i=31;i>=0;i--){
int v=1-s[i];
if(!trie[u].son[v])v^=1;
else ans+=(1<<i);
u=trie[u].son[v];
}
return ans;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&x[i]);
}
int sumx=0;num=0;
for(int i=1;i<=n;i++)
sumx^=x[i],insert(sumx),
l[i]=max(l[i-1],find(sumx));
memset(trie,0,num+1);
sumx=0;num=0;
for(int i=n;i>=1;i--)
sumx^=x[i],
insert(sumx),
r[i]=max(r[i+1],find(sumx));
for(int i=1;i<n;i++)ans=max(ans,l[i]+r[i+1]);
printf("%d\n",ans);
}

可持久化Trie

Trie学习总结的更多相关文章

  1. 可持久化trie学习笔记

    其实很早之前就想学习可持久化trie,不过由于换队友等情况,还是优先去学数论和计算几何,今天突然心血来潮学了一发可持久化trie,感觉还是蛮简单的,不过由于自己很长时间没写过可持久化了,都快忘了是个什 ...

  2. 字典树trie学习

    字典树trie的思想就是利用节点来记录单词,这样重复的单词可以很快速统计,单词也可以快速的索引.缺点是内存消耗大 http://blog.csdn.net/chenleixing/article/de ...

  3. 可持久化trie 学习总结

    QAQ 以前一直觉得可持久化trie很难,今天强行写了一发觉得还是蛮简单的嘛 自己的模板是自己手写的,写了几道题目并没有出过错误 THUSC的第二题的解法五貌似就是可持久化trie,时间复杂度O(60 ...

  4. Trie学习笔记

    Trie(字典树) 基本数据结构 实际是:对于每个字符串组的每一个不同前缀建立节点 基本代码 void Insert(char *s,int p){ int now=0; int l=strlen(s ...

  5. 字典树(Trie)学习笔记

    目录 什么是字典树 如何存储字典树 如何查找字符串有没有出现 第一个图的那种线段树 应用 例题 1.统计难题 2.P2580 于是他错误的点名开始了 什么是字典树 上图来自luogu题解 这是一种字典 ...

  6. 标准Trie字典树学习一:原理解析

    特别声明: 博文主要是学习过程中的知识整理,以便之后的查阅回顾.部分内容来源于网络(如有摘录未标注请指出).内容如有差错,也欢迎指正! 系列文章: 1. 字典树Trie学习一:原理解析 2.字典树Tr ...

  7. 博客索引and题目列表

    目录 笔记整理 计划 要学的东西 缺省源 要做的题 搜索 高斯消元 矩阵 排列组合 2019.7.9 2019.7.10 kmp ac自动机 2019.7.11 2019.7.15 笔记整理 1.同余 ...

  8. [特别篇] 记JZ冬令营(Finished)

    1.16 走错班了, 去了全是大佬的1班, 然后灰溜溜滚回2班了. 去参加开营仪式. 然而昏昏欲睡... 实在太累了澡也没洗.. 群英云集, 多是感慨. 当时依依惜别和铮铮誓言, 在重逢中无语凝噎. ...

  9. python利用Trie(前缀树)实现搜索引擎中关键字输入提示(学习Hash Trie和Double-array Trie)

    python利用Trie(前缀树)实现搜索引擎中关键字输入提示(学习Hash Trie和Double-array Trie) 主要包括两部分内容:(1)利用python中的dict实现Trie:(2) ...

随机推荐

  1. 最小生成树计数 模板 hdu 4408

    题意是给定n个点,m条边的无向图,求最小生成树的个数对p取模. 用kruscal计算最小生成树时,每次取连接了两个不同联通块的最小的边.也就是先处理d1条c1长度的边,再处理d2条c2长度的边.长度相 ...

  2. python 网页中文显示Unicode码

    print repr(a).decode("unicode–escape") 注:a是要输出的结果,

  3. 微信小程序使用wxParse实现接入富文本编辑

    简介 微信小程序中比如活动说明,简介这样的图文介绍说明页面,后台通常配置成富文本编辑框,由后台直接输入内容,然后在小程序界面展现. 但是富文本编辑提取到内容是html格式的,写法与小程序的wxml并不 ...

  4. Springboot中定时任务的使用

    在springboot中已经集成了定时任务,只需要在启动类上加注解@EnableScheduling即可 例如: 添加类加上@Component注解,添加方法加上@Scheduler即可

  5. python浅析对return的理解

    函数外部的代码要想获取函数的执行结果,就可以在函数里面用return语句,把结果返回. return 代表一个函数的终止,如果return 后面带一个print 或者return  ,则后面的不执行 ...

  6. Cosmetic Airless Bottles To Meet Practical Requirements

    Today, people use cosmetic bottles, many of which are in cosmetic airless bottles. We can use them, ...

  7. Mybatis 元素类型为 "resultMap" 的内容必须匹配 "(constructor?,id*,result*,association*,collection*,discriminat

    <resultMap id="BaseResultMap" type="com.youotech.tl_cons_credit_rating.entity.TL_C ...

  8. mybatis中条件查询大于等于和小于等于写法

    原符号 < <= > >= & ' "替换符号 < <= > >= & &apos; " createDat ...

  9. 对 Element UI table中数据进行二次处理

    (1)<el-table-column>标签加上 :formatter="dateFormat" <el-table-column prop="Star ...

  10. RT_THREAD之nano学习

    nona版本为精简版本,只保留FISH(选配)最小内核,可以适配STM的STD.HAL/LL库,需要手动进行移植:现在可以在KEIL MDK/CUBEMX中进行集成,也可以RT-Thread Nano ...