01Trie 学习笔记

Preface

\(01Trie\) 是指字符集为 \(\{0, 1\}\)\(Trie\)
\(01Trie\) 可以用来维护一些数字的异或和,支持修改(删除 + 重新插入),和全局加 \(1\)(即:让其所维护所有数值递增 \(1\),本质上是一种特殊的修改操作)。

Operation

\(w[p]\) 保存节点 \(p\) 到其父亲的边上被经过的次数。
\(xorv[p]\) 保存以 \(p\) 为根的子树中保存的数的异或和。
常识:\(p -> ch[p][0]\)\(0\) 边,\(p -> ch[p][1]\)\(1\)

New

inline int New() {
	++tot;
	ch[tot][0] = ch[tot][1] = w[tot] = xorv[tot] = 0;
	return tot;
}

pushup

可能比较难懂得就是这句话:

xorv[p] ^= (xorv[ch[p][1]] << 1) | (w[ch[p][1]] & 1);

这是因为在 \(01Trie\) 中数是倒着存的,所以是子节点的值乘以二。
\(w[ch[p][1]]\) 中保存了节点 \(ch[p][1]\)\(p\) 的边被经过的次数,因为这条边代表的是 \(1\),所以 \(w[ch[p][1]]\) 保存的是 \(p\) 节点表示的这一位上 \(1\) 的个数,我们只需要知道当前位的答案,所以我们只关心它的奇偶性,只有当这一位 \(1\) 的个数位奇数个时异或起来的答案才为 \(1\), 所以这一位的答案就是 \(w[ch[p][1]] \& 1\)

inline void pushup(int p) {
	w[p] = xorv[p] = 0;
	if(ch[p][0]) {
		w[p] = w[ch[p][0]];
		xorv[p] = xorv[ch[p][0]] << 1;
	}
	if(ch[p][1]) {
		w[p] += w[ch[p][1]];
		xorv[p] ^= (xorv[ch[p][1]] << 1) | (w[ch[p][1]] & 1);
	}
}

insert

就是直接递归,看在二进制下当前位是 \(1\) 还是 \(0\),进入相应的子节点即可。
这里我们强制让每个叶子节点的深度为 \(21\),是为了方便我们全体加 \(1\) 的操作。

inline void insert(int &p, int x, int dep) {
	if(!p) p = New();
	if(dep > 20) return (void) (w[p]++);
	insert(ch[p][x & 1], x >> 1, dep + 1);
	pushup(p);
}

erase

\(insert\)

inline void erase(int &p, int x, int dep) {
	if(dep > 20) return (void) (w[p]--);
	erase(ch[p][x & 1], x >> 1, dep + 1);
	pushup(p);
}

update

考虑二进制意义下的加一操作。
我们只用从最低位开始找到第一个 \(0\),把它变成 \(1\),在把它后面的 \(1\) 变成 \(0\) 即可。
对应在 \(01Trie\) 上就是从根节点开始找到第一个 \(1\) 为止。
当前这位是 \(1\),我们将其换成 \(0\), 当前这位是 \(0\),我们将其换成 \(1\)
因为每个节点只会有一个子节点。
所以换了位之后的 \(ch[p][0]\) 其实是原来的 \(ch[p][1]\),如果它不为空,说明这一位原来是 \(1\), 我们并没有找到第一个 \(0\),继续递归下去。
找到第一个 \(0\) 就意味着换了位之后的 \(ch[p][0]\) 为空,也就是原来的 \(ch[p][1]\) 为空。结束递归。

inline void update(int p) {
	swap(ch[p][0], ch[p][1]);
	if(ch[p][0]) update(ch[p][0]);
	pushup(p);
}

merge

就和线段树合并的操作是很类似的,把 \(b\) 的信息合并到 \(a\) 上即可。

int merge(int a, int b) {
        if(!a || !b) return a | b;
	w[a] += w[b], xorv[a] ^= xorv[b];
	ch[a][0] = merge(ch[a][0], ch[b][0]);
	ch[a][1] = merge(ch[a][1], ch[b][1]);
	return a;
}

Code

namespace Trie {
	int ch[M][2], w[M], xorv[M], tot = 0;
	inline int New() {
		++tot;
		ch[tot][0] = ch[tot][1] = w[tot] = xorv[tot] = 0;
		return tot;
	}
	inline void pushup(int p) {
		w[p] = xorv[p] = 0;
		if(ch[p][0]) {
			w[p] = w[ch[p][0]];
			xorv[p] = xorv[ch[p][0]] << 1;
		}
		if(ch[p][1]) {
			w[p] += w[ch[p][1]];
			xorv[p] ^= (xorv[ch[p][1]] << 1) | (w[ch[p][1]] & 1);
		}
	}
	inline void insert(int &p, int x, int dep) {
		if(!p) p = New();
		if(dep > 20) return (void) (w[p]++);
		insert(ch[p][x & 1], x >> 1, dep + 1);
		pushup(p);
	}
        inline void erase(int &p, int x, int dep) {
		if(dep > 20) return (void) (w[p]--);
		erase(ch[p][x & 1], x >> 1, dep + 1);
		pushup(p);
	}
	inline void update(int p) {
		swap(ch[p][0], ch[p][1]);
		if(ch[p][0]) update(ch[p][0]);
		pushup(p);
	}
	int merge(int a, int b) {
		if(!a || !b) return a | b;
		w[a] += w[b], xorv[a] ^= xorv[b];
		ch[a][0] = merge(ch[a][0], ch[b][0]);
		ch[a][1] = merge(ch[a][1], ch[b][1]);
		return a;
	}
}

Excersice

省选联考 2020 A 卷 树
Ynoi2010 Fusion tree

posted @ 2021-09-14 21:30  init-神眷の樱花  阅读(429)  评论(0)    收藏  举报