发现每个点距离为 \(1\) 的节点就是儿子或者父亲,因此可以把儿子和父亲分开来算。
计算父亲是很容易的,直接维护 \(a\) 的值。对于操作 \(1\) ,在父亲上标记就行了,表示这个点进行过几次的操作 \(1\)。
对于每个节点维护儿子,就会发现是要维护:单点加入、单点删除、全局 \(+1\),全局异或和。
如果对 01-trie 比较熟悉,可以发现 01-trie 可以维护这个东西。从低位到高位建立 01-trie,大致是要维护每个节点的子树的异或和 \(val\),还有每个节点到父亲的边权 \(w\)(指这条边被经过了几次)。
由于是异或,会发现可以只考虑 \(w\) 的奇偶性。于是 push_up 大致是这样的:
inline void push_up(int u)
{
w[u]=0,val[u]=0;
if (nxt[u][0])
{
w[u]^=w[nxt[u][0]];
val[u]^=(val[nxt[u][0]]<<1);
}
if (nxt[u][1])
{
w[u]^=w[nxt[u][1]];
val[u]^=(val[nxt[u][1]]<<1);
if (w[nxt[u][1]]==1) val[u]^=1;
}
}
注意到只考虑 \(w\) 的奇偶性,因此 insert 和 delete 本质相同。你可以理解为,先加入一个数 \(x\),要把它删除等同于再加一个 \(x\),这样异或和为 \(0\) 了。所以其实没有必要写两个函数(但是好像基本上题解都写了)。
如何处理全局 \(+1\)?从低到高考虑,每一位 \(1\) 变成 \(0\) 同时进位,\(0\) 变成 \(1\) 不进位。递归做就行了。
void add(int x)
{
swap(nxt[x][0],nxt[x][1]);
if (nxt[x][0]) add(nxt[x][0]);
push_up(x);
}
于是就可以维护了。注意一些实现的细节。