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
}

P3293 [SCOI2016] 美味

posted @ 2024-08-23 16:33  endswitch  阅读(104)  评论(0)    收藏  举报