关于Trie

Trie是一种维护字符串集合的数据结构,通常这个字符串也可以作为整数来完成一些二进制上的维护,名叫01trie。
trie的维护思路很简单,trie建h层,h>=所要维护的最长字符串的长度,然后对于每一个节点,分别有26个子节点表示26个小写字母。这样层层相连必定可以表示出来所有字符串。我们要维护字符串集合,只需将所有字符串都加入这个trie中,然后对于每个字符末尾的地方做一个标记,即可做到最简单的trie功能。
模板题:

于是他错误的点名开始了

动态开点建trie

代码如下

#include<bits/stdc++.h>
using namespace std;
#define int long long
int const maxn = 1e6 + 10;
int T;

int nxt[maxn][30],cnt = 1;

bool exist[maxn];
bool vis[maxn];
void insert(string s){
	int p = 0;
	int len = s.size();
	for(int i = 0;i < len;i ++){
		int c = s[i] - 'a';
		if(!nxt[p][c]){
			nxt[p][c]= ++ cnt;
		}
		p = nxt[p][c];
	}
	exist[p]++;
}
int find(string s){
	int p = 0;
	int len = s.size();
	for(int i = 0;i < len;i ++){
		int c = s[i] - 'a';
		if(!nxt[p][c])return 0;
		p = nxt[p][c];
	}
	if(exist[p]){
		if(!vis[p]){
			vis[p] = 1;
			return 1;
		}
		return 2;
	}else {
		return 0;
	}
}


signed main(){
	int n;
	cin >> n ;
	for(int i = 1;i <= n;i ++){
		string s;
		cin >> s;
		insert(s);
	}
	int m;
	cin >> m ;
	while(m--){
		string s;
		cin >> s;
		int tmp = find(s);
		if(tmp==1){
			cout << "OK"<<'\n';
		}else if(tmp==2){
			cout <<"REPEAT"<<'\n';
		}else {
			cout << "WRONG"<<'\n';
		}
	}
	return 0;
}

更为广泛的还是将trie数应用在维护整数的二进制集合

01Trie

Trie的每一个节点都有两个子节点分别表示0和1。基本操作和trie差别不大,无非建树的时候倒序枚举i,这样可以从高位向低位进行枚举,方便利用二进制某一位比前面所有位数之和加起来还大的性质进行贪心操作。

例题:Xor sum

板子题
代码如下:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int const maxn = 5e6+10;
int nxt[maxn][3];
int exist[maxn];
int cnt;
void insert(int a){
	int p = 0;
	for(int i = 33;i >= 0;i --){
		int tmp = ((a >> i)&1);
		if(!nxt[p][tmp]){
			nxt[p][tmp] = ++cnt;
		}
		p = nxt[p][tmp];
	}
	exist[p] = a;
}
int find(int a){
	int p = 0;
    for(int i = 33;i >= 0;i --){
    	int tmp = ((a >> i)&1);
		if(nxt[p][tmp^1]){
			p = nxt[p][tmp^1];
		}else{
			p = nxt[p][tmp];
		}
    }
    return exist[p];
}
int s[maxn];
signed main(){
	int n,m;
	cin >> n >> m ;
	for(int i = 1;i <= n;i ++){
		cin >> s[i];
		insert(s[i]);
	}
	for(int i = 1;i <= m;i ++){
		int k;
		cin >> k;
		cout << find(k)<<'\n';
	}
	return 0;
}

例题:Chip Factory

问题的主要困难是解决a,b,c不能相等的问题,我们只需n^2枚举a,b的时候将a,b在Trie的tot--即可,再查找最合适的c,找完之和再加上。
代码如下

#include<bits/stdc++.h>
using namespace std;
#define int long long
int const maxn = 3e4;
int nxt[maxn][3];
int exist[maxn];
int cnt;
int tot[maxn];
void insert(int a,int num){
	int p = 0;
	for(int i = 33;i >= 0;i --){
		int tmp = ((a >> i)&1);
		if(!nxt[p][tmp]){
			nxt[p][tmp] = ++cnt;
		}
		p = nxt[p][tmp];
		tot[p]+=num;
	}
	exist[p] = a;
}
int find(int a){
	int p = 0;
    for(int i = 33;i >= 0;i --){
    	int tmp = ((a >> i)&1);
		if(tot[nxt[p][tmp^1]] > 0){
			p = nxt[p][tmp^1];
		}else{
			p = nxt[p][tmp];
		}
    }
    return exist[p];
}
int s[maxn];
int ans;
signed main(){
	int n;
	cin >> n;
	for(int i = 1;i <= n;i ++){
		cin >> s[i];
		insert(s[i],1);
	}
	for(int i = 1;i < n;i ++){
		for(int j = i + 1;j <= n;j ++){
			insert(s[i],-1);
			insert(s[j],-1);
			ans = max(ans,(s[i]+s[j])^find(s[i]+s[j]));
			insert(s[i],1);
			insert(s[j],1);
		}
	}
	cout << ans<<'\n';
	return 0;
}

例题:最长异或路径

设d[x]为x到根节点距离,x与y之间的异或和显然等于d[x]^d[y],正常处理即可

#include<bits/stdc++.h>
using namespace std;
#define int long long
int const maxn = 2e6+10;
int nxt[maxn][2];
int cnt;
void insert(int a){
	int p = 0;
	for(int i = 30;i >= 0;i--){
		bool tmp = a&(1<<i);
		if(!nxt[p][tmp]){
			nxt[p][tmp] = ++cnt;
		}
		p = nxt[p][tmp];
	}  
}
int find(int a){
	int p = 0,ans = 0;
	for(int i = 30;i >= 0;i --){
		bool tmp = a&(1<<i);
		if(nxt[p][tmp^1]){
			ans += (1<<i);
			p = nxt[p][tmp^1];
	    }else {
	    	p = nxt[p][tmp];
	    }
	}
	return ans;
}
int tot;
struct node{
	int v,w,nxt;
}e[maxn];
int head[maxn];
void add(int u,int v,int w){
	e[++tot].v = v;
	e[tot].w = w;
	e[tot].nxt = head[u];
	head[u] = tot; 
}
int d[maxn];
void dfs(int u,int fa){
     for(int i = head[u];i;i = e[i].nxt){
     	int v = e[i].v;
     	if(v==fa)continue;
     	d[v] = d[u]^e[i].w;
     	dfs(v,u);
     }
}



signed main(){
	int n;
	cin >> n ;
	for(int i = 1;i <= n- 1;i ++){
		int u,v,w;
		cin >> u >> v >> w;
		add(u,v,w);
		add(v,u,w);
	}
	dfs(1,0);
	for(int i = 1;i <= n;i ++){
		insert(d[i]);
	}
	int ans = 0;
	for(int i = 1;i <= n;i ++){
		ans = max(ans,find(d[i]));
	}
	cout << ans;
	return 0;
}

可持久化Trie

正常的可持久化思路,没做修改就继承,做修改就修改节点对应权值,并新建节点。
例题:最大异或和
考虑考虑从a[1]到a[n]建一棵可持久化trie,节点权值为出现数量,每次查询的时候就前缀和思想减就行,查询可以转化为求[l-1,r-1]中与x^s[n]异或后最大的值,注意特判l-1为0的情况,可能不需要异或任何数。
代码如下:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int const maxn = 6e5+10;
struct T{
	int cnt = 0,rt[maxn],ch[maxn*33][2];
	int val[maxn*33];
	void insert(int now,int lst,int v){
		for(int i = 27;i >= 0;i --){
			bool tmp = v&(1<<i);
			val[now] = val[lst]+1;
		    ch[now][tmp] = ++cnt;
			ch[now][tmp^1] = ch[lst][tmp^1];
			now = ch[now][tmp];
			lst = ch[lst][tmp];
		}
		val[now] = val[lst]+1;
	}
	int query(int l,int r,int v){
		int ans = 0;
		for(int i = 27;i >= 0;i --){
			bool tmp = v&(1<<i);
			int tmk = val[ch[r][!tmp]] - val[ch[l][!tmp]];
			if(tmk){
				ans += (1<<i);
				r = ch[r][!tmp];
				l = ch[l][!tmp];
			}else {
				l = ch[l][tmp];
				r = ch[r][tmp];
			}
		}
		return ans;
	}
}trie;
int a[maxn];
signed main(){
	int n,m;
	cin >> n >> m ;
	trie.rt[0] = ++trie.cnt;
	for(int i = 1;i <= n;i ++){
		cin >> a[i];
		a[i] = a[i]^a[i-1];
        trie.cnt++;
        trie.rt[i] = trie.cnt;
   		trie.insert(trie.rt[i],trie.rt[i-1],a[i]);
	}
	
	for(int i = 1;i <= m;i ++){
		char op;
		cin >> op;
		if(op=='A'){
			n++;
			cin >> a[n];
			a[n] = a[n]^a[n-1];
			trie.cnt++;
			trie.rt[n] = trie.cnt;
			trie.insert(trie.rt[n],trie.rt[n-1],a[n]);
		}else {
			int l,r,x;
			cin >> l >> r >> x;
			l--,r--;
			if(l==0){
				cout << max(trie.query(0,trie.rt[r],x^a[n]),x^a[n]) << '\n';
			}else{
				cout << trie.query(trie.rt[l-1],trie.rt[r],x^a[n]) << '\n';
			}
		}
	}
	return 0;
}

例题:ALO
该题考了两个知识点,一个是快速求出序列中每个数的前驱和后继,以及用可持久化Trie维护区间内的二进制整数。求前驱后继,可以用pre和nxt存当前数在序列中位置的前一个位置和后一个位置,再将其排序,然后从小到大依次得到每个数的前驱和后继,然后删除该点。
代码如下:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int const maxn = 2e5+10;
struct T{
	int cnt = 0,rt[maxn],ch[maxn*33][2];
	int val[maxn*33];
	
	void insert(int now,int lst,int v){
		for(int i = 30;i >= 0;i --){
			bool tmp = v&(1<<i);
			val[now] = val[lst]+1;
		    ch[now][tmp] = ++cnt;
			ch[now][tmp^1] = ch[lst][tmp^1];
			now = ch[now][tmp];
			lst = ch[lst][tmp];
		}
		val[now] = val[lst]+1;
	}
	int query(int l,int r,int v){
		int ans = 0;
		for(int i = 30;i >= 0;i --){
			bool tmp = v&(1<<i);
			int tmk = val[ch[r][!tmp]] - val[ch[l][!tmp]];
			if(tmk){
				ans += (1<<i);
				r = ch[r][tmp^1];
				l = ch[l][tmp^1];
			}else {
				l = ch[l][tmp];
				r = ch[r][tmp];
			}
		}
		return ans;
	}
}trie;
int a[maxn],id[maxn];
int pre[maxn],nxt[maxn];
bool cmp(int x,int y){
	return a[x] < a[y];
}
signed main(){
	int n;
	cin >> n ;
	trie.rt[0] = ++trie.cnt;
	for(int i  = 1;i <= n;i ++){
		cin >> a[i];
		trie.rt[i] = ++trie.cnt;
		trie.insert(trie.rt[i],trie.rt[i-1],a[i]);
		pre[i] = i - 1;
		nxt[i] = i + 1;
		id[i] = i;
	}
	int ans = 0;
	sort(id+1,id+n+1,cmp);
	for(int i = 1;i <= n;i ++){
		int l = pre[id[i]],r = nxt[id[i]];
		if(l>0){
			ans = max(ans,trie.query(trie.rt[pre[l]],trie.rt[r - 1],a[id[i]]));
		}
		if(r<=n){
			ans = max(ans,trie.query(trie.rt[l],trie.rt[nxt[r] - 1],a[id[i]])); 
		}
		nxt[l] = r;
		pre[r] = l;
	}
	cout << ans;
}

总结:Trie树,通常用来维护多个字符串组成的集合,并判断某个字符串是否存在在集合中,及某个字符串是否是集合中一个字符串的子串。01trie树则通常用来维护一个整数集合,获取集合中与某个数异或的最大值,或者存储某个数的出现次数。可持久化01trie数则通常用来维护序列中某个区间内的整数集合。

posted @ 2023-10-13 21:48  瑞恩尼lower  阅读(33)  评论(0)    收藏  举报