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;
}
}

浙公网安备 33010602011771号