关于trie

​ 其实字典树和以上两种算法有很大不同,但是hash由于其优秀的应用,导致有些字符串查找用hash也是可行的.

​ 字典树中支持添加,查找,区间查询(可持久化字典树),而且在异或操作上有更加好的操作;

前置知识

​ 树的基本构造;

入坑

​ 字典树是通过动态建点,而形成的树,基本数组有两维, \(tr[x][to]\) 中第一维存的是节点标号,而第二维存的是当字符为 \(to\) 时通向的节点;

基本操作

​ 我当时入门是学的是这道题;

给你一些初始字符串,询问,给你一个字符串,这个字符串在这个初始字符串中是否存在

​ 当时使用hash写的,但是没过;

​ 现在我们可以用字典树先存一下初始字符串,然后在树上匹配,单次时间复杂度 \(O(n)\) ;

code

#include<bits/stdc++.h>
using namespace std;
int n,m,trie[300007][27],num[300007],sz;
bool vis[300007]; void build(char a[]){
int now=0;
for(int i=0;i<strlen(a);i++){
if(!trie[now][a[i]-'a']) trie[now][a[i]-'a']=++sz;
now=trie[now][a[i]-'a'];
}
num[now]++;
} int exam(char a[]){
int now=0;
for(int i=0;i<strlen(a);i++){
if(!trie[now][a[i]-'a']) return 0;
now=trie[now][a[i]-'a'];
}
if(!num[now]) return 0;
if(vis[now]) return 1;
vis[now]=1; return 2;
} int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
char a[60];
scanf("%s",a);
build(a);
}
scanf("%d",&m);
for(int i=1;i<=m;i++){
char a[60];
scanf("%s",a);
int x=exam(a);
switch(x){
case 0:{
printf("WRONG\n");
break;
}
case 1:{
printf("REPEAT\n");
break;
}
case 2:{
printf("OK\n");
break;
}
}
}
}

​ 我在hash中介绍了map,这里其实也可以用map存字符串,但是其时间复杂度比原来的多了一个 \(logn\) ,写法虽然简单但是时间并不优秀;

code
#include<bits/stdc++.h>
using namespace std;
map<string,int>p;
int n,m;
string s;
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
cin>>s;
p[s]=1;
}
scanf("%d",&m);
for(int i=1;i<=m;i++){
cin>>s;
if(p[s]==1){
puts("OK"),p[s]=2;
}else if(p[s]==0){
puts("WRONG");
}else if(p[s]==2){
puts("REPEAT");
}
}
return 0;
}

拓展

​ 异或,是我们经常会见到的,但是如何高效的处理异或信息是一个让人头痛的事,而字典树为我们提供了策略.

0/1串树

​ \(0/1\) 串树,常用来储存一个2进制数字,我们知道异或正是与二进制有关,那么我们是否可以找在字典树上操作序列呢?

​ 显然是可以的,我们在一个节点,分别走0通向的节点和1通向的节点,那么贪心地操作,这样一定是异或对最大值,反之,都走0或者1可以有效地得到最小值,

例题1

最长异或路径 ,这个例题应该比较合适;

给定一棵 \(n\) 个点的带权树,结点下标从 \(1\) 开始到 \(N\) 。寻找树中找两个结点,求最长的异或路径。

异或路径指的是指两个结点之间唯一路径上的所有边权的异或。

​ 显然,我们可以将每个点到根root的异或和保存一下,然后将其加入字典树,现在我们要求的就是最大的异或数对,跟上面说的一样,我们只要从字典树顶端开始BFS,就可以得到最大异或数对.

code

#include<bits/stdc++.h>
#define maxn 100007
#define mp(x,y,z,w) (nd){x,y,z,w}
using namespace std;
int n,head[maxn],dis[maxn],tr[maxn*32][2],ed[maxn*32],val[maxn*32];
int tot,cent,ans[34],ol;
struct node{
int next,to,w;
}edge[maxn<<2];
struct nd{
int x,y,val,dep;
}; template<typename type_of_scan>
inline void scan(type_of_scan &x){
type_of_scan f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
template<typename top,typename... tops>
inline void scan(top &x,tops&... X){
scan(x),scan(X...);
} inline void add(int u,int v,int w){
edge[++cent]=(node){head[u],v,w};head[u]=cent;
edge[++cent]=(node){head[v],u,w};head[v]=cent;
} void dfs(int x,int fa){
for(int i=head[x];i;i=edge[i].next){
int y=edge[i].to;
if(y==fa) continue;
dis[y]=dis[x]^edge[i].w;
dfs(y,x);
}
}//求异或和 void insert(int x,int id){
int now=0;
for(int i=30;i>=0;i--){
int pos=!!(x&(1<<i));
if(!tr[now][pos]) tr[now][pos]=++tot;
now=tr[now][pos];
}
ed[tot]=id,val[tot]=x;
}//树中保存 queue<nd>q;
int bfs(){
q.push(mp(0,0,0,0));
while(!q.empty()){
int x=q.front().x,y=q.front().y,d=q.front().dep;
int val=q.front().val;q.pop();
if(d>30) return ans[d];//到底层了,此时一定是最优解
if(ans[d]>val) continue;//减支
if(tr[x][0]&&tr[y][1]){
q.push(mp(tr[x][0],tr[y][1],val<<1|1,d+1));
ans[d+1]=max(ans[d+1],val<<1|1);
}
if(tr[x][1]&&tr[y][0]){
if(x!=y) q.push(mp(tr[x][1],tr[y][0],val<<1|1,d+1));
ans[d+1]=max(ans[d+1],val<<1|1);
}else{
if(!tr[x][0]||!tr[y][1]){
if(tr[x][0]&&tr[y][0]){
q.push(mp(tr[x][0],tr[y][0],val<<1,d+1));
ans[d+1]=max(ans[d+1],val<<1);
}
if(tr[x][1]&&tr[y][1]){
q.push(mp(tr[x][1],tr[y][1],val<<1,d+1));
ans[d+1]=max(ans[d+1],val<<1);
}
}
}//分类讨论
}
} int main(){
scan(n);
for(int i=1,u,v,w;i<=n-1;i++) scan(u,v,w),add(u,v,w);
dfs(1,0);
for(int i=1;i<=n;i++) insert(dis[i],i),ol=max(ol,dis[i]);
printf("%d\n",max(ol,bfs()));
return 0;
}

例题2

CF888G Xor-MST

已知一个 \(n\) 个节点的无向完全图,每个节点的编号为 \(a_i\) , \(i\) 与 \(j\) 的边的权值是 \(a_i\) ^ \(a_j\) ,求该图的 \(MST\) 的权值;

​ 我们可以想一下 \(kruskal\) 算法的过程,那么我们也可以每次寻找最小值,可以通过在trie上BFS得到;

​ 值得注意的是,所有分叉点的个数为建边个数(去掉两点权值相同),那么其实直接寻找即可;

#include<bits/stdc++.h>
#define maxn 200007
#define mp(x,y,z,w) (node){x,y,z,w}
#define ll long long
using namespace std;
int n,m,a[maxn],tr[maxn*33][2],ed[maxn*33],tot;
int fa[maxn];bool vis[maxn*33];
vector<int>dep[34];
struct node{
int x,y,d,ans;
}; template<typename type_of_scan>
inline void scan(type_of_scan &x){
type_of_scan f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
template<typename type_of_print>
inline void print(type_of_print x){
if(x>9) print(x/10);
putchar(x%10+'0');
} void add(int x,int id){
int now=0,d=0;
for(int i=30;i>=0;i--,d++){
int pos=(!(x&(1<<i)));
if(!tr[now][pos]) tr[now][pos]=++tot;
if(tr[now][pos^1]&&!vis[now]) vis[now]=1,dep[d].push_back(now);
now=tr[now][pos];
}
if(ed[now]) fa[ed[now]]=id;
ed[now]=id;
} queue<node>q;
int find(int now,int d,int &x,int &y){
int f1=tr[now][0],f2=tr[now][1];
while(!q.empty()) q.pop();
int ans[32];
memset(ans,127,sizeof ans);ans[d]=1;
q.push((node){f1,f2,d,1});
while(!q.empty()){
x=q.front().x,y=q.front().y;
int dx=q.front().d,ans1=q.front().ans;q.pop();
if(ed[x]&&ed[y]){
x=ed[x],y=ed[y];
break;
}
if(ans1>ans[dx]) continue;
if(tr[x][0]&&tr[y][0]) q.push(mp(tr[x][0],tr[y][0],dx+1,ans1<<1)),ans[dx+1]=min(ans[dx+1],ans1<<1);
if(tr[x][1]&&tr[y][1]) q.push(mp(tr[x][1],tr[y][1],dx+1,ans1<<1)),ans[dx+1]=min(ans[dx+1],ans1<<1);
else if(!tr[x][0]||!tr[y][0]){
if(tr[x][1]&&tr[y][0])
q.push(mp(tr[x][1],tr[y][0],dx+1,ans1<<1|1)),ans[dx+1]=min(ans[dx+1],ans1<<1|1);
if(tr[y][1]&&tr[x][0])
q.push(mp(tr[x][0],tr[y][1],dx+1,ans1<<1|1)),ans[dx+1]=min(ans[dx+1],ans1<<1|1);
}
}
return ans[30];
} int get(int x){return fa[x]==x?x:fa[x]=get(fa[x]);} ll ans; int main(){
// freopen("tree.in","r",stdin);
// freopen("tree.out","w",stdout);
scan(n);
for(int i=1;i<=n;i++) fa[i]=i;
for(int i=1;i<=n;i++){
scan(a[i]);add(a[i],i);
}
for(int i=30;i>=0;i--){
if(!dep[i].size()) continue;
for(int j=0;j<dep[i].size();j++){
int x,y,f;
f=find(dep[i][j],i,x,y);
if(get(x)==get(y)) continue;
fa[get(x)]=get(y);ans+=1ll*f;
}
}
printf("%lld",ans);
}
/*
5
1 2 3 4 5
*/

可持久化串树

​ 可持久化串树,即可在区间中查询的串树,与可持久数组有些类似,只是前者用trie,后者用主席树罢了.

​ 建树时,我们可以再建一个节点,然后继承上一个节点的信息,然后再建一个新节点去保存自己的信息.

​ 区间查询时,我们进入右端点的trie节点,为了限制左边界,我们可以在建图时将其序号标上,在搜索到小于左端点编号时跳过,去寻找另一个节点即可.

建树(0/1trie)

void build(int x,int last,int &f,int pos){
int now;now=f=++tot;
for(int i=lim,ol=(x&(1<<i))>>i;i>=0;i--,ol=(x&(1<<i))>>i){
trie[now][0]=trie[last][0];
trie[now][1]=trie[last][1];//继承
last=trie[last][ol];
trie[now][ol]=++tot;//开拓新节点
mark[tot]=pos;//记录序号
now=trie[now][ol];//向下拓展
}
ending[now]=x;//结尾数字
}

查询(0/1trie)

int dfs(int x,int f,int op){
int now=f;
for(int i=lim,ol=(x&(1<<i))>>i;i>=0;i--,ol=(x&(1<<i))>>i){
if(trie[now][ol^1]&&mark[trie[now][ol^1]]>=op) now=trie[now][ol^1];
else now=trie[now][ol];
}
return ending[now];
}

​ 这里的查询是在一段区间中查询异或k的最大值;

例题1

最大异或和,模版题;

​ 我们就按照上面的步骤即可;

code
#include<bits/stdc++.h>
#define maxn 1200007
using namespace std;
int n,m,trie[maxn*10][2],a[maxn],sum[maxn],tot;
int ending[maxn*10],mark[maxn*10],lim,id[maxn]; template<typename type_of_scan>
inline void scan(type_of_scan &x){
type_of_scan f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
template<typename top,typename... tops>
inline void scan(top &x,tops&... X){
scan(x),scan(X...);
} void build(int x,int last,int &f,int pos){
int now;now=f=++tot;
for(int i=lim,ol=(x&(1<<i))>>i;i>=0;i--,ol=(x&(1<<i))>>i){
trie[now][0]=trie[last][0];
trie[now][1]=trie[last][1];
last=trie[last][ol];
trie[now][ol]=++tot;
mark[tot]=pos;
now=trie[now][ol];
}
ending[now]=x;
} int dfs(int x,int f,int op){
int now=f;
for(int i=lim,ol=(x&(1<<i))>>i;i>=0;i--,ol=(x&(1<<i))>>i){
if(trie[now][ol^1]&&mark[trie[now][ol^1]]>=op) now=trie[now][ol^1];
else now=trie[now][ol];
}
return ending[now];
} int main(){
// freopen("cin.in","r",stdin);
scan(n,m);
for(int i=1;i<=n;i++){
scan(a[i]),sum[i]=sum[i-1]^a[i];
lim=max(sum[i],lim);
}
lim=(int)log2(1e7)+1;
for(int i=1;i<=n;i++)
build(sum[i],id[i-1],id[i],i);
for(int i=1,l,r,x;i<=m;i++){
char s=getchar();
while(s!='A'&&s!='Q') s=getchar();
if(s=='A') scan(x),n++,sum[n]=sum[n-1]^x,build(sum[n],id[n-1],id[n],n);
else if(s=='Q'){
scan(l,r,x);
printf("%d\n",(sum[n]^x)^dfs((sum[n]^x),id[r-1],l-1));
}
}
return 0;
}

例题2

异或粽子,可持久化trie查询区间最大异或值;

​ 这道题的思路可以从 超级钢琴 中得到.

​ 超级钢琴的思路是将权值处理出来,与区间信息一起保存在优先队列中,然后每次取出最大值,再更新左右区间即可;

​ 而这道题与其不同的是,这里将权值处理出来的方式不同,这里运用可持久化trie,然后在区间查询最大异或值,其他的与超级钢琴几乎一致;

#include<bits/stdc++.h>
#define maxn 1000007
#define ll long long
using namespace std;
ll n,k,trie[maxn*23][2],mark[maxn*23],val[maxn*23];
ll sum[maxn],ans,tot,lim=33,a[maxn],id[maxn];
struct node{
ll val,l,r,pos,ori;
};
priority_queue<node>q; template<typename type_of_scan>
inline void scan(type_of_scan &x){
type_of_scan f=1;x=0;char s=getchar();
while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
x*=f;
}
template<typename top,typename... tops>
inline void scan(top &x,tops&... X){
scan(x),scan(X...);
} bool operator <(node a,node b){
return a.val<b.val;
} void build(ll x,ll last,ll &f,ll pos){
ll now;now=f=++tot;
for(ll i=lim,ol=(x&(1<<i))>>i;i>=0;i--,ol=(x&(1<<i))>>i){
trie[now][0]=trie[last][0];
trie[now][1]=trie[last][1];
last=trie[last][ol];
trie[now][ol]=++tot;
mark[tot]=pos;
now=trie[now][ol];
}
val[now]=x;
} ll query(ll x,ll f,ll op,ll &pos){
ll now=f;
for(ll i=lim,ol=(x&(1<<i))>>i;i>=0;i--,ol=(x&(1<<i))>>i){
if(trie[now][ol^1]&&mark[trie[now][ol^1]]>=op) now=trie[now][ol^1];
else now=trie[now][ol];
}
return pos=mark[now],val[now];
} int main(){
// freopen("xor.in","r",stdin);
// freopen("xor.out","w",stdout);
scan(n,k);
for(ll i=1;i<=n;i++){
scan(a[i]),sum[i]=sum[i-1]^a[i];
build(sum[i],id[i-1],id[i],i);
}
for(ll i=1;i<=n;i++){
ll ol,pos;
ol=query(sum[i-1],id[n],i,pos);
q.push((node){ol^sum[i-1],i,n,pos,sum[i-1]});
}
for(ll i=1;i<=k;i++){
if(q.empty()) continue;
node x=q.top();ans+=x.val;q.pop();
ll ol,pos;
if(x.l<x.pos){
ol=query(x.ori,id[x.pos-1],x.l,pos);
q.push((node){ol^x.ori,x.l,x.pos-1,pos,x.ori});
}
if(x.pos<x.r){
ol=query(x.ori,id[x.r],x.pos+1,pos);
q.push((node){ol^x.ori,x.pos+1,x.r,pos,x.ori});
}
}
printf("%lld\n",ans);
}

后记

​ 这里只是总结了一下trie的用法,我见到的主要还是0/1trie,以后见到还会再加入;

TO BE CONTINUED

trie浅谈的更多相关文章

  1. (转)浅谈trie树

    浅谈Trie树(字典树)         Trie树(字典树) 一.引入 字典是干啥的?查找字的. 字典树自然也是起查找作用的.查找的是啥?单词. 看以下几个题: 1.给出n个单词和m个询问,每次询问 ...

  2. 浅谈可持久化Trie与线段树的原理以及实现(带图)

    浅谈可持久化Trie与线段树的原理以及实现 引言 当我们需要保存一个数据结构不同时间的每个版本,最朴素的方法就是每个时间都创建一个独立的数据结构,单独储存. 但是这种方法不仅每次复制新的数据结构需要时 ...

  3. 浅谈 Fragment 生命周期

    版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Fragment 文中如有纰漏,欢迎大家留言指出. Fragment 是在 Android 3.0 中 ...

  4. 浅谈 LayoutInflater

    浅谈 LayoutInflater 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/View 文中如有纰漏,欢迎大家留言指出. 在 Android 的 ...

  5. 浅谈Java的throw与throws

    转载:http://blog.csdn.net/luoweifu/article/details/10721543 我进行了一些加工,不是本人原创但比原博主要更完善~ 浅谈Java异常 以前虽然知道一 ...

  6. 浅谈SQL注入风险 - 一个Login拿下Server

    前两天,带着学生们学习了简单的ASP.NET MVC,通过ADO.NET方式连接数据库,实现增删改查. 可能有一部分学生提前预习过,在我写登录SQL的时候,他们鄙视我说:“老师你这SQL有注入,随便都 ...

  7. 浅谈WebService的版本兼容性设计

    在现在大型的项目或者软件开发中,一般都会有很多种终端, PC端比如Winform.WebForm,移动端,比如各种Native客户端(iOS, Android, WP),Html5等,我们要满足以上所 ...

  8. 浅谈angular2+ionic2

    浅谈angular2+ionic2   前言: 不要用angular的语法去写angular2,有人说二者就像Java和JavaScript的区别.   1. 项目所用:angular2+ionic2 ...

  9. iOS开发之浅谈MVVM的架构设计与团队协作

    今天写这篇博客是想达到抛砖引玉的作用,想与大家交流一下思想,相互学习,博文中有不足之处还望大家批评指正.本篇博客的内容沿袭以往博客的风格,也是以干货为主,偶尔扯扯咸蛋(哈哈~不好好工作又开始发表博客啦 ...

随机推荐

  1. 详解Vue中的computed和watch

    作者:小土豆 博客园:https://www.cnblogs.com/HouJiao/ 掘金:https://juejin.cn/user/2436173500265335 1. 前言 作为一名Vue ...

  2. 断言封装之key检查及kv实战示例

    ️️️️️️️️️️️️️️️️️️️️️️️️️️️️️ 测试: 断言处理: demo_04.pyimport jsonjson_obj = {"access_token":&q ...

  3. FlatBuffers使用小结

    最近做一个Android APP,由于离线业务需求,需要在启动APP时候同步大量数据到APP上,遇到了JSON性能瓶颈.从下方的图片中可以看出,当使用 json 传输数据,在解析json的时候会产生大 ...

  4. ctfhub技能树—文件上传—00截断

    什么是00截断 相关教程:http://www.admintony.com/%E5%85%B3%E4%BA%8E%E4%B8%8A%E4%BC%A0%E4%B8%AD%E7%9A%8400%E6%88 ...

  5. thinkpad8平板安装win10系统

    ThinkPad8 因为是平板电脑,只有一个micro USB接口,常规安装没法使用鼠标或键盘进行输入,所以难倒很多人. 幸好前段时间买了根otg线和3.0usb hub,安装方法记录如下: 准备:U ...

  6. 响应式编程库RxJava初探

    引子 在读 Hystrix 源码时,发现一些奇特的写法.稍作搜索,知道使用了最新流行的响应式编程库RxJava.那么响应式编程究竟是怎样的呢? 本文对响应式编程及 RxJava 库作一个初步的探索. ...

  7. TCP客户端程序

    TCP客户端程序的函数调用顺序为:socket -> connect -> send/recv socket.send和recv函数在TCP服务器程序中已经说过了,这里就不赘述了. con ...

  8. C# socket 阻止模式与非阻止模式应用实例

    问题概述 最近在处理一些TCP客户端的项目,服务端是C语言开发的socket. 实际项目开始的时候使用默认的阻塞模式并未发现异常.代码如下 1 public class SocketService 2 ...

  9. 面对key数量多和区间查询低效问题:Hash索引趴窝,LSM树申请出场

    摘要:Hash索引有两个明显的限制:(1)当key的数量很多时,维护Hash索引会给内存带来很大的压力:(2)区间查询很低效.如何对这两个限制进行优化呢?这就轮到本文介绍的主角,LSM树,出场了. 我 ...

  10. 一文搞定全场景K3s离线安装

    作者简介 王海龙,Rancher中国社区技术经理,负责Rancher中国技术社区的维护和运营.拥有6年的云计算领域经验,经历了OpenStack到Kubernetes的技术变革,无论底层操作系统Lin ...