dsu on tree
\(dsu\ on\ tree\),即树上启发式合并。它要满足:
- 只有询问,且是离线(无修改操作)
- 只涉及到子树(或者可以把问题转化为子树上操作)
- 子树之间不会互相干扰
它和莫队的思想其实有一点像,都是“优雅的暴力”
具体的实现过程:
- 对于树上一个节点\(x\),先处理轻子树的答案,统计完只影响到\(x\),不向上保留
- 再处理重子树答案,统计完不仅会影响到\(x\),还要向上保留
- 回溯时,算出\(x\)轻子树的答案\((calc(1))\)。如果不能保留,则删掉贡献\((calc(-1))\)。(\(calc()\)函数暴力算)
- 关键思想就是删掉轻儿子,保留重儿子
这样做的时间复杂度是对的,因为一个点到根路径上不超过\(\log n\)条轻边
具体代码:
void dfs(int x, int f)
{
sz[x] = 1; int mx = 0;
for (int i = hd[x]; i; i = nxt[i])
{
int y = to[i];
if (y == f) continue;
dep[y] = dep[x] + 1;
dfs(y, x), sz[x] += sz[y];
if (sz[y] > mx) mx = sz[y], ms[x] = y; // 重儿子
}
return;
}
void calc(int x, int f, int o)
{
if (o == 1) // do something (统计子树内答案)
else // do something (消除影响)
for (int i = hd[x]; i; i = nxt[i])
{
int y = to[i];
if (y == f || y == nms) continue;
calc(y, x, o);
}
return;
}
void dsu(int x, int f, int o)
{
for (int i = hd[x]; i; i = nxt[i])
{
int y = to[i];
if (y == f || y == ms[x]) continue;
dsu(y, x, -1); // 轻子树
}
if (ms[x]) dsu(ms[x], x, 1), nms = ms[x]; // 重子树
calc(x, f, 1), nms = 0;
ans[x] = ... // 更新答案
if (o == -1) calc(x, f, -1);
return;
}