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