关于正确性的感性理解
在x和y有一个为空时,可以直接返回那个不为空的。这样做不会导致给一些节点加一些不该加的边的原因可以这么理解:当你想给某个节点添加一个儿子的时候,必然是两颗树节点都有这个节点,那么此时代码便会新建一个节点,故这样的merge不会导致连出不该连的边。
实现细节
这两种写法都是正确的:
使用主席树的插入方法,无论如何都新建节点。那么此时插入操作的执行位置无影响。
void ins(int &u, int l, int r, int x, int p) {
int tmp = u; u = ++tcnt, t[u] = t[tmp];
if(l == r) {
if(tmp) adde(u+n, tmp+n, MAXN);
adde(u+n, p, MAXN);
return ;
}
if(x <= mid) ins(t[u].ls, l, mid, x, p);
else ins(t[u].rs, mid+1, r, x, p);
}
int merge(int x, int y, int l, int r) {
if(!x || !y) return x + y;
int u = ++tcnt;
if(l == r) {
adde(u+n, x+n, INF), adde(u+n, y+n, INF);
return u;
}
if(t[x].ls || t[y].ls) t[u].ls = merge(t[x].ls, t[y].ls, l, mid);
if(t[x].rs || t[y].rs) t[u].rs = merge(t[x].rs, t[y].rs, mid+1, r);
return u;
}
void dfs(int u) {
size[u] = 1;
for(ri i = 0; i < adj[u].size(); ++i) {
dfs(adj[u][i]), root[u] = merge(root[u], root[adj[u][i]], 1, MAXN);
}
ins(root[u], 1, MAXN, val[u], u);
}
插入采用权值线段树插入,仅节点为空时新建。这样则需要保证插入操作在合并操作之前,否则会导致如果一个父节点直接等同于一个子节点,那么在插入父节点的时候,子节点也会被插入。
但其实没啥必要判断空节点,毕竟每次都是直接插入空树。
void ins(int &u, int l, int r, int x, int p) {
//if(!u) u = ++tcnt;
u = ++tcnt;
if(l == r) {
adde(u+n, p, MAXN);
return ;
}
if(x <= mid) ins(t[u].ls, l, mid, x, p);
else ins(t[u].rs, mid+1, r, x, p);
}
int merge(int x, int y, int l, int r) {
if(!x || !y) return x + y;
int u = ++tcnt;
if(l == r) {
adde(u+n, x+n, INF), adde(u+n, y+n, INF);
return u;
}
if(t[x].ls || t[y].ls) t[u].ls = merge(t[x].ls, t[y].ls, l, mid);
if(t[x].rs || t[y].rs) t[u].rs = merge(t[x].rs, t[y].rs, mid+1, r);
return u;
}
void dfs(int u) {
size[u] = 1;
ins(root[u], 1, MAXN, val[u], u);
for(ri i = 0; i < adj[u].size(); ++i) {
dfs(adj[u][i]), root[u] = merge(root[u], root[adj[u][i]], 1, MAXN);
}
}