trie 树

一、普通 t r i e \rm trie trie

t r i e \rm trie trie 树又称字典树、前缀树,它把很多单词放到一棵树上,使用空间去换时间。

LUOGU2580 于是他错误的点名开始了

Description \text{Description} Description

给定 n n n 个互不相同且只含小写字母的字符串,以及 m m m 个问题,每个问题给出一个字符串 S S S

  1. S S S 在这 n n n 个字符串中出现过:

    1. S S S 是第一次被问,输出OK
    2. S S S 已经被问过,输出REPEAT
  2. S S S 在这 n n n 个字符串中未出现过,输出WRONG

Solution \text{Solution} Solution

我们设 t r i e p , c trie_{p,c} triep,c 来表示 t r i e trie trie p p p 的儿子中第 c c c 个所对应的节点,其中 c c c 可取 1 ∼ 26 1\sim26 126,分别代表 a ∼ z \text{a}\sim\text{z} az

1. 插入

当我们要插入字符串 S S S 的时候,遍历整个 S S S,同时用一个 p o s pos pos 记录当前访问到的节点, p o s pos pos 一开始指向根节点,即 p o s = 0 pos=0 pos=0,当现在遍历到第 i i i 位时,令 c = S[i] - 'a'

  1. t r i e p o s , c = 0 trie_{pos,c}=0 triepos,c=0,说明这个位置还没有节点,那就新建一个;
  2. p o s = t r i e p o s , c pos=trie_{pos,c} pos=triepos,c
int tot;
int trie[MAXN][26]; //MAXN为所有字符串的总长度

void insert(char *s)
{
	int len = strlen(s), pos = 0;
	for (int i = 0; i < len; i++)
	{
		int c = s[i] - 'a';
		if (!trie[pos][c])
		{
			trie[pos][c] = ++tot; //新建节点
		}
		pos = trie[pos][c];
	}
}

例如现在要插入单词 at,sea,see,meat \text{at,sea,see,meat} at,sea,see,meat

首先插入 at \text{at} at
然后是 sea \text{sea} sea
插入 see \text{see} see 时, t r i e \rm trie trie 上已有前缀 se \text{se} se,所以变成了这样:
最后是 meat \text{meat} meat

2. 查询

和插入差不多。

  1. t r i e p o s , c = 0 trie_{pos,c}=0 triepos,c=0 时,说明 S S S 不在 t r i e \rm trie trie 中;
  2. 若遍历完整个字符串,说明 S S S t r i e \rm trie trie 中。

对于本题,我们用一个 v i s vis vis 数组来记录 S S S 是否是第一次被询问。

int search(char *s)
{
	int len = strlen(s), pos = 0;
	for (int i = 0; i < len; i++)
	{
		int c = s[i] - 'a';
		if (!trie[pos][c])
		{
			return 0; //WRONG
		}
		pos = trie[pos][c];
	}
	if (vis[pos])
	{
		return 2; //REPEAT
	}
	vis[pos] = true;
	return 1; //OK
}

t r i e \rm trie trie 树的插入,查询时间复杂度均为 O ( l e n ) \mathcal{O}(len) O(len)。空间复杂度为 O ( ∑ l e n × 26 ) \mathcal{O}(\sum len\times26) O(len×26),这是典型的空间换时间。

Code \text{Code} Code

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int MAXN = 5e5 + 5;

int tot;
int trie[MAXN][26];
bool vis[MAXN];

void insert(char *s)
{
	int len = strlen(s), pos = 0;
	for (int i = 0; i < len; i++)
	{
		int c = s[i] - 'a';
		if (!trie[pos][c])
		{
			trie[pos][c] = ++tot;
		}
		pos = trie[pos][c];
	}
}

int search(char *s)
{
	int len = strlen(s), pos = 0;
	for (int i = 0; i < len; i++)
	{
		int c = s[i] - 'a';
		if (!trie[pos][c])
		{
			return 0;
		}
		pos = trie[pos][c];
	}
	if (vis[pos])
	{
		return 2;
	}
	vis[pos] = true;
	return 1;
}

char s[55];

int main()
{
	int n, m;
	scanf("%d", &n);
	while (n--)
	{
		scanf("%s", s);
		insert(s);
	}
	scanf("%d", &m);
	while (m--)
	{
		scanf("%s", s);
		int res = search(s);
		if (!res)
		{
			puts("WRONG");
		}
		else if (res == 1)
		{
			puts("OK");
		}
		else
		{
			puts("REPEAT");
		}
	}
	return 0;
}

二、 01 \rm 01 01- t r i e \rm trie trie

01 \rm 01 01- t r i e \rm trie trie 是一种 很 大 聪 明 的 \color{White}{很大聪明的} t r i e \rm trie trie 的变形,它的树上只有 0 0 0 1 1 1

LOJ#10050. 「一本通 2.3 例 2」The XOR Largest Pair

Description \text{Description} Description

给定 N N N 个整数,从中选出两个进行异或运算,问得到的结果最大是多少。

Solution \text{Solution} Solution

考虑贪心。在二进制下,一个数的第 i i i 位为 0 0 0,那我们就尽量给他找个 1 1 1,反之就找个 0 0 0,这样就能使异或后的数有尽量多的位为 1 1 1

那么可以把所有的数转成二进制后扔到 t r i e \rm trie trie 上,这样 t r i e \rm trie trie 上就只有 0 0 0 1 1 1,这就是 01 \rm 01 01- t r i e \rm trie trie

但是实际上并不需要先转成二进制,我们可以在 insert ⁡ \operatorname{insert} insert 的时候顺带处理。

void insert(int val)
{
	int pos = 0;
	for (int i = 30; i >= 0; i--)
	{
		int c = (val >> i) & 1; //取出第i位
		if (!trie[pos][c])
		{
			trie[pos][c] = ++tot;
		}
		pos = trie[pos][c];
	}
}

然后 search ⁡ \operatorname{search} search 的时候就按照上面的贪心去找,如果实在没有那就只能取相同的了。

int search(int val)
{
	int pos = 0, res = 0;
	for (int i = 30; i >= 0; i--)
	{
		int c = (val >> i) & 1;
		if (trie[pos][c ^ 1]) //如果有就取
		{
			res |= (1 << i);
			pos = trie[pos][c ^ 1];
		}
		else
		{
			pos = trie[pos][c];
		}
	}
	return res;
}

Code \text{Code} Code

#include <iostream>
#include <cstdio>
using namespace std;

const int MAXN = 1e5 + 5;

int tot;
int trie[MAXN * 32][2];

void insert(int val)
{
	int pos = 0;
	for (int i = 30; i >= 0; i--)
	{
		int c = (val >> i) & 1;
		if (!trie[pos][c])
		{
			trie[pos][c] = ++tot;
		}
		pos = trie[pos][c];
	}
}

int search(int val)
{
	int pos = 0, res = 0;
	for (int i = 30; i >= 0; i--)
	{
		int c = (val >> i) & 1;
		if (trie[pos][c ^ 1])
		{
			res |= (1 << i);
			pos = trie[pos][c ^ 1];
		}
		else
		{
			pos = trie[pos][c];
		}
	}
	return res;
}

int a[MAXN];

int main()
{
	int n, ans = 0;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		scanf("%d", a + i);
		ans = max(ans, search(a[i]));
		insert(a[i]);
	}
	printf("%d\n", ans);
	return 0;
}

LOJ#10056. 「一本通 2.3 练习 5」The XOR-longest Path

Descripton \text{Descripton} Descripton

给定一棵 n n n 个点的带权树,求树上最长的异或和路径。

Solution \text{Solution} Solution

预处理出 d e p dep dep 数组, d e p i dep_i depi 表示节点 i i i 到根节点的路径上所有边的边权的异或和。

比如我们要求节点 x x x 到节点 y y y 路径上所有边的边权和,设 LCA ⁡ ( x , y ) = z \operatorname{LCA}(x,y)=z LCA(x,y)=z,那么 d e p x ⊕ d e p y dep_x\oplus dep_y depxdepy 多算了 2 2 2 d e p z dep_z depz,根据异或的运算律,这 2 2 2 d e p z dep_z depz 异或在一起会抵消,所以 d e p x ⊕ d e p y dep_x\oplus dep_y depxdepy 就等于 x x x y y y 路径上的边的边权异或和。

所以预处理出 d e p dep dep 数组然后就变成了上面那题(((

Code \text{Code} Code

#include <iostream>
#include <cstdio>
using namespace std;

const int MAXN = 1e5 + 5;

int tot;
int trie[MAXN * 100][2];

void insert(int val)
{
	int pos = 0;
	for (int i = 30; i >= 0; i--)
	{
		int c = (val >> i) & 1;
		if (!trie[pos][c])
		{
			trie[pos][c] = ++tot;
		}
		pos = trie[pos][c];
	}
}

int search(int val)
{
	int pos = 0, res = 0;
	for (int i = 30; i >= 0; i--)
	{
		int c = (val >> i) & 1;
		if (trie[pos][c ^ 1])
		{
			res |= (1 << i);
			pos = trie[pos][c ^ 1];
		}
		else
		{
			pos = trie[pos][c];
		}
	}
	return res;
}

int cnt;
int head[MAXN], dep[MAXN];

struct edge
{
	int to, dis, nxt;
}e[MAXN << 1];

void add(int u, int v, int w)
{
	e[++cnt] = edge{v, w, head[u]};
	head[u] = cnt;
}

void dfs(int u, int fa)
{
	for (int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (v == fa)
		{
			continue;
		}
		dep[v] = dep[u] ^ e[i].dis;
		dfs(v, u);
	}
}

int main()
{
	int n, ans;
	scanf("%d", &n);
	for (int i = 1; i < n; i++)
	{
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		add(u, v, w);
		add(v, u, w);
	}
	dfs(1, 0);
	for (int i = 1; i <= n; i++)
	{
		insert(dep[i]);
		ans = max(ans, search(dep[i]));
	}
	printf("%d\n", ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值