01 trie

前言

引用一句刘邦的话:“有时 01 Trie 胜于 01 Trie。”

正文

前景提要

  • Trie(串串咕了 qwq)
  • 位运算
  • 贪心

01 trie

简介

实质上是字符集只有 \(\left \{ 0,1 \right \}\) 的 trie。

由于每一个自然数一定可以进行二进制拆分,所以一个数其实可以看成一个二进制下的 01 串。

所以 01 trie 是可以高效维护序列上的位运算问题的。

另外由于 01 trie 维护的字符串实质上是二进制拆分后的一个数,其长度为 \(O(\log V)\),所以 01 trie 的树高是 \(\log V\) 的,时间复杂度是 \(O(n \log V)\) 的。

代码解析

01 trie 不同于其它 ds,其运作主要是基于贪心实现的,并且要求选手对二进制又一定的熟悉程度。

下面以 CF923C 为例,对 01 trie 的代码进行解析。

CF923C

  • 分析

1.题目要求让 \(c\) 序列字典序最小,那么就是让 \(c\) 越靠前的元素越小

2.此时题目转化为异或最小值问题,可以考虑使用 01 trie

3.倘若我们考虑完第 \(i\) 个元素,则第 \(i\) 个元素往后不能继续贡献,因此要在 01 trie 上删除第 \(i\) 个元素

  • 代码解析

\(\circ\) 变量定义

const int V = 31; // 表示维护的值域拆成二进制下的最高位数
int tot; // 01 trie 上节点总数
struct Trie {
	int sz // 可以用来维护诸如 trie 树上的子树和之类的东西,此处并未用到
	, cnt // 维护到当前节点的前缀的出现次数
	, ch[2]; // 字符集,01 trie 的字符集就是 0/1
	
	inline int operator [] (const bool x) const { // 个人代码习惯重载
		return ch[x];
	}
} t[N << 5]; // 保险起见未限制空间时开 5 倍空间

\(\circ\) 插入数字

inline void insert(int x) {
	int p = 0; // 当前节点
	
	for(int i = V - 1 ; ~ i ; -- i) {
		bool w = x & (1 << i); // 要插入的数字从高往低的二进制位
		
		if(! t[p][w]) t[p].ch[w] = ++ tot; // 若此前没有节点则新建
		
		p = t[p][w], 	++ t[p].cnt; // 迭代节点,统计前缀出现次数
	}
}

\(\circ\) 查询


// 基本上 01 trie 的入门题变化就出现在查询时的贪心考虑

inline int query(int x) {
	int p = 0, res = 0; // 当前节点与使得与 x 异或值最小的答案
	
	for(int i = V - 1 ; ~ i ; -- i) {
		bool w = x & (1 << i);
		
		if(! t[t[p][w]].cnt) w ^= 1; // 贪心,尽量要让二进制下两位相同异或和才能小,若不同才选择另外一个
		
		res = (res << 1) | w; // 统计答案
		
		p = t[p][w];
		
		-- t[p].cnt; // 查询时顺便删除
	}
	
	return res ^ x; // 记得异或 x
}
posted @ 2024-08-23 16:33  end_switch  阅读(4)  评论(0编辑  收藏  举报