Trie树

思路

\(trie\),又称字典树,是一种树形结构,可以在 \(O(n)\) 的时间内查找字符串是否存在。\(Trie\) ​​​​树根节点为空,支持动态开点,每个点储存儿子的编号。\(Trie\) ​​​​树通常是存储字符串,但有时也可以存储别的信息。空间复杂度为 \(O(n\times len)\) ​​相对较大。不过 \(Trie\)​ 树​还有一些别的变体,例如 \(01Trie\) ​可以是常用于处理异或操作的一种数据结构。

例如有字符串 \(ABCAAA\)\(ABCABA\)\(BA\) 三个串,其 \(trie\) 是这样的。

\(Code\)

inline void Build(string s){				//构造Trie树
	int l = s.length();
	int now = 0;							//Trie的指针 
	for(register int i = 0; i < l; ++i){
		if(AC[now].vis[s[i]-'a'] == 0)		//Trie树没有这个子节点
			AC[now].vis[s[i]-'a'] = ++cnt;	//构造出来
		now = AC[now].vis[s[i]-'a'];		//向下构造 
	}
	AC[now].end += 1;						//标记单词结尾 
}

例题一:于是他错误的点名开始了

思路

\(trie\) 树版题,构建 \(trie\) 树后直接对 \(end\) 的值进行判定即可。

\(Code\)

#include<bits/stdc++.h>
using namespace std;

string s;
int n, m;
int cnt;
struct Trie{
	int vis[26], end;
}tree[500005];

template<typename T>
inline void read(T&x){
	x = 0; char q; bool f = 1;
	while(!isdigit(q = getchar()))	if(q == '-')	f = 0;
	while(isdigit(q)){
		x = (x<<1) + (x<<3) + (q^48);
		q = getchar();
	} 
	x = f?x:-x;
}

template<typename T>
inline void write(T x){
	if(x < 0){
		putchar('-');
		x = -x;
	}
	if(x > 9)	write(x/10);
	putchar(x%10^48);
}

inline void build(string s){
	int len = s.size();
	int u = 0;
	for(register int i = 0; i < len; ++i){
		if(!tree[u].vis[s[i]-'a'])	tree[u].vis[s[i]-'a'] = ++cnt;
		u = tree[u].vis[s[i]-'a'];
	}
	tree[u].end = 1;
}

inline short search(string s){
	int len = s.size();
	int u = 0;
	for(register int i = 0; i < len; ++i){
		if(!tree[u].vis[s[i]-'a'])	return 0;
		u = tree[u].vis[s[i]-'a'];
	}
	if(tree[u].end == 2)	return 2;
	tree[u].end = 2;
	return 1;
}

int main(){
	read(n);
	for(register int i = 1; i <= n; ++i){
		cin >> s;
		build(s);
	}
	read(m);
	for(register int i = 1; i <= m; ++i){
		cin >> s;
		short t = search(s);
		if(t == 0)	cout << "WRONG" << endl;
		if(t == 1)	cout << "OK" << endl;
		if(t == 2)	cout << "REPEAT" << endl;
	}
	return 0;
} 

例题二:\(Secret\) \(Message\) \(G\)

思路

很典型的版题,直接对文本串建立 \(Trie\)​​ 树,然后跑模式串即可,跑完会出现一下几种情况。

\(end\) 表示该节点有多少个文本串结束,\(sum\) 表示该节点有多少串经过(结束也算)。

  1. 模式串是文本串前缀,直接输出一路加的 \(end\)​ 值即可。
  2. 文本串是模式串前缀,之前的 \(end\) 值减最后一个节点的 \(end\) 值加最后一个节点的 \(sum\)​ 值即可,其实很容易理解,\(sum\) 表示了经过及结束的点,所以减去 \(end\) 再加 \(sum\) 即为经过了此处点不以此为结尾的串,也就是模式串的前缀。

\(Code\)

#include<bits/stdc++.h>
using namespace std;

int n, m;
struct Trie{
	int son[2], sum, end;					//sum为每个点的经过次数,end为每个点作为结尾的次数,son[i]表示i儿子编号
}tree[500005];
int a[10005], num, cnt;

inline void build(int x){					//建立trie树
	int u = 0;
	for(register int i = 1; i <= x; ++i){
		if(!tree[u].son[a[i]])	tree[u].son[a[i]] = ++cnt;
		u = tree[u].son[a[i]];
		tree[u].sum++;						//每个节点经过即+1
	}
	tree[u].end++;							//终止点+1
}

inline void search(int x){					//查找字符串
	int u = 0, ans = 0;
	for(register int i = 1; i <= x; ++i){
		if(!tree[u].son[a[i]]){				//如果已经未建立该点,说明在trie树中已经与他无交集了,则输出答案
			cout << ans;
			putchar('\n');
			return;
		}
		u = tree[u].son[a[i]];
		ans += tree[u].end;					//ans加上以这个点结尾的答案
	}
	ans -= tree[u].end;						//ans减去以这个点为结尾的数量
	ans += tree[u].sum;						//ans加上经过终止点的数量
	cout << ans;
	putchar('\n');
	return;
}

int main(){
    cin >> m >> n;
	for(register int i = 1; i <= m; ++i){
		cin >> num;
		for(register int j = 1; j <= num; ++j)	cin >> a[j];
		build(num);
	}
	for(register int i = 1; i <= n; ++i){
		cin >> num;
		for(register int j = 1; j <= num; ++j)	cin >> a[j];
		search(num);
	}
	return 0;
}

例题三:最长异或路径

思路

看到为异或操作,首先想到 \(Trie\)​​,可以发现这样一个性质:若用 \(ans_{u,v}\) 表示 \(u\)\(v\) 的异或距离,因为相同两个数异或值为 \(0\),所以其值就是 \(ans_{u,\gcd_{u,b}}\oplus ans_{v,\gcd_{u,v}}\),因此,求二者的异或距离就可以根据二者到根的距离的异或值求解。

于是直接根据路径上的异或值构建一棵 \(01Trie\) ​​树,每个路径代表它到根的异或路径。通过贪心的思想,高位为 \(1\)​​ 总是比高位为 \(0\)​​ 大,所以如果异或值为 \(1\)​​ 的儿子节点存在就走这个节点,如果不存在或者另一个儿子异或值为 \(0\)​​​ 就走原节点。

首先做一次 \(dfs\) ​记录路径异或值,然后根据异或值建立一棵 \(Trie\) ​树,最后在 \(Trie\) ​树基础上进行贪心即可。

\(Code\)

#include<bits/stdc++.h>
using namespace std;

int n, x, y, z;
int head[100005], ne[200005], e[200005], val[200005], idx;		//边的信息
int Xor[100005], cnt, maxn;				//Xor[i]表示i点到根节点异或值

struct Trie{
	int son[2];						//表示01儿子编号
}tree[3100005];						//操作数*31

inline void add(int u, int v, int num){
	e[++idx] = v;
	ne[idx] = head[u];
	val[idx] = num;
	head[u] = idx;
}

inline void dfs(int u, int fa){			//dfs记录每个点到根的异或值
	for(register int i = head[u]; i; i = ne[i]){
		int v = e[i];
		if(v == fa)	continue;
		Xor[v] = Xor[u]^val[i];
		dfs(v, u);
	}
}

inline void build(int x, int u){		//建立trie树
	for(register int i = 1<<30; i; i >>= 1){
		bool f = x&i;
		if(!tree[u].son[f])	tree[u].son[f] = ++cnt;
		u = tree[u].son[f];
	}
}

inline int query(int x, int u){			//查询最大值
	int ans = 0;
	for(register int i = 1<<30; i; i >>= 1){
		bool f = x&i;
		if(tree[u].son[f^1])	ans += i, u = tree[u].son[f^1];		//若有节点为1,则走这个节点
		else	u = tree[u].son[f];									//若没有,则继续当前的的节点
	}
	return ans;
}

int main(){
    cin >> n;
	for(register int i = 1; i < n; ++i){
		cin >> x >> y >> z;
		add(x, y, z);
		add(y, x, z);
	}
	dfs(1, 0);
	for(register int i = 1; i <= n; ++i)	build(Xor[i], 0);
	for(register int i = 1; i <= n; ++i)	maxn = max(maxn, query(Xor[i], 0));		//对于每个点,都查询一次最大值
    cout << maxn;
	return 0;
}
posted @ 2022-01-17 21:00  Zzzzzzzm  阅读(60)  评论(0)    收藏  举报