zoukankan      html  css  js  c++  java
  • trie浅谈

    关于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
    ");
    				break;
    			}
    			case 1:{
    				printf("REPEAT
    ");
    				break;
    			}
    			case 2:{
    				printf("OK
    ");
    				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
    ",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
    ",(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
    ",ans);
    }
    

    后记

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

    TO BE CONTINUED
  • 相关阅读:
    convert data to numeric element wise , python,pandas
    return sql ret
    kettle学习
    kettle异常
    c# + python
    mysql表是否存在
    python configparser
    pandas sql execute
    apply map applymap
    sql 2008 中不能创建数据库关系图
  • 原文地址:https://www.cnblogs.com/waterflower/p/11769109.html
Copyright © 2011-2022 走看看