dsu on tree

推荐GsjzTle的博客

\(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;
}
posted @ 2021-04-07 15:22  andysj  阅读(67)  评论(0编辑  收藏  举报