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. Abaqus 载荷分类(部分)

    目录 1. 集中载荷 1.1 集中载荷施加方法 1.2 定义集中跟随力 1.3 指定文件定义集中节点力 2. 分布载荷 2.1 分布载荷分类 3. 热载荷 3.1 模拟热辐射 3.2 直接定义热流量 ...

  2. 安装Tengine版本的nginx

    安装tengine版nginx #!/bin/bash yum install epel-release -y yum install gcc unzip gcc-c++ git wget bind- ...

  3. 如何在Linux中显示和设置主机名(适用ubantu、centos等版本)

    随着连接到网络的计算机数量越来越多,每一台计算机都需要有一个属性来区别于其它计算机.和现实世界中的人一样,计算机也有一个叫做hostname(主机名)的属性. 什么是hostname 从它的操作手册来 ...

  4. mount命令实际操作样例

    本篇文章主要介绍了如何在Linux(CentOS 7)命令行模式安装VMware Tools,具有一定的参考价值,感兴趣的小伙伴们可以参考一下. 本例中为在Linux(以CentOS 7为例)安装VM ...

  5. maven设置指定jdk版本

    今天心血来潮准备折腾一下jeecg,去下载了一个maven版本的项目,既然下载了maven版的,当然就要配置好maven环境了. 因为之前简单学习过maven,当时使用的版本是3.3.9的,但是今天在 ...

  6. springboot+mybatis报错Invalid bound statement (not found)

    今天做项目时报了一个错提示说Invalid bound statement (not found),也就是说mapper接口绑定.xml文件出错了,找不到指定的sql:原因是程序没有把.xml文件编译 ...

  7. Hadoop3.1.1源码Client详解 : Packet入队后消息系统运作之ResponseProcessor(ACK接收)

    该系列总览: Hadoop3.1.1架构体系——设计原理阐述与Client源码图文详解 : 总览 紧接着上一篇文章: Hadoop3.1.1源码Client详解 : Packet入队后消息系统运作之D ...

  8. 【原】移动端vue页面点透事件 - 分析与解决

    近期项目遇到了vue页面事件被带到下一个页面的问题,也就是我们常说的点透事件,主要表现在android机器上,花了不少时间折腾,简单做下总结~ vue页面之间的切换通过Vue Router的route ...

  9. openstack自动化搭建脚本

    Openstack平台部署+节点扩容 1)搭建脚本 #!/bin/bash #openstack私有云平台部署 #脚本使用前提:三台虚拟机openstack(ip地址:.11至少4G内存,100G硬盘 ...

  10. TensorFlow:使用inception-v3实现各种图像识别

    程序来自博客: # https://www.cnblogs.com/felixwang2/p/9190740.html上面这个博客是一些列的,所以可以从前往后逐一练习. # https://www.c ...