P6018 [Ynoi2010] Fusion tree
lxl 说过邻域信息维护父亲一定死,所以数据结构维护每个点的儿子,特判父亲。
考虑每个点的数据结构需要支持什么操作:全局加一,单点修改,全局异或和。用 01Trie 维护。
与维护最大异或对的 01Trie 不同,从低位到高位建树,每个点上维护子树异或和,修改时 push up。
考虑怎么 push up。从 $u$ 往其 $0/1$ 子树中走到叶子形成的数,就是从 $u$ 的 $0/1$ 孩子走到叶子形成的数后面加一位 $0/1$,
所以 $u$ 子树数集包含 $u$ 的 $0$ 孩子子树数集中所有数后面加一位 $0$,$u$ 的 $1$ 孩子子树数集中所有数后面加一位 $1$,
即 $u$ 子树数集中,$u$ 的 $0$ 孩子子树异或和后面加一位 $0$;若 $u$ 的 $1$ 孩子子树数集大小为奇数,其异或和后面加一位 $1$,否则加 $0$。
考虑怎么全局加一。加一的过程实际上是从低到高按位取反,若当前位取反后为 $0$ 则继续取反下一位,
所以从上往下交换 $0/1$ 孩子,然后继续交换 $0$ 孩子的 $0/1$ 孩子。
指针非常好写。
#include <cstdio>
#include <algorithm>
#define V(x) a[x] + z[f[x]]
#define O(x, k) if(f[x]) C(V(x), 0, r[f[x]]);a[x] += k;if(f[x]) C(V(x), 0, r[f[x]])
using namespace std;
struct E{int v, t;}e[1000050];
int n, m, c, a[500050], z[500050], f[500050], h[500050];
void A(int u, int v) {e[++c] = {v, h[u]};h[u] = c;}
struct T
{
T *c[2];int s, w;T() : c{0, 0}, w(0) {}void u()
{
if(c[s = w = 0]) s ^= c[0]->s, w ^= c[0]->w << 1;
if(c[1]) s ^= c[1]->s, w ^= c[1]->w << 1 | c[1]->s;
}
}*r[500050];
void C(int x, int d, T *&p)
{
if(!p) p = new T;if(d >= 20) return void(p->s ^= 1);
C(x, d + 1, p->c[x >> d & 1]);p->u();
}
void M(T *p) {if(p) swap(p->c[0], p->c[1]), M(p->c[0]), p->u();}
void D(int u)
{
for(int i = h[u], v;i;i = e[i].t)
if(!r[v = e[i].v]) C(a[v], 0, r[f[v] = u]), D(v);
}
int main()
{
scanf("%d%d", &n, &m);
for(int i = 1, u, v;i < n;++i)
scanf("%d%d", &u, &v), A(u, v), A(v, u);
for(int i = 1;i <= n;++i) scanf("%d", a + i);D(1);
for(int i = 0, o, x, v;i < m;++i)
{
scanf("%d%d", &o, &x);
switch(o)
{
case 1: ++z[x];M(r[x]);if(f[x]) {O(f[x], 1);}break;
case 2: scanf("%d", &v);O(x, -v);break;
case 3: printf("%d\n", (r[x] ? r[x]->w : 0) ^ V(f[x]));break;
}
}
return 0;
}