dsu on tree 学习笔记

观前提醒:博主唐

一个炒鸡简单的东西哦,水一篇 blog

u1s1 NOIP 前一天我好像随便学了一下这玩意……

dsu on tree 用来解决一些数据结构无法维护(最经典的如数颜色)的子树查询问题,不允许修改,原理是改变 dfs 顺序来实现一个「优雅的暴力」(跟莫队的出发点其实很类似)。(dsu on tree 与 dsu 没有任何关系,倒是用了启发式合并的思想(想当年我自己 yy 启发式合并)

我们考察一棵二叉树。如果把每个叶子都视为一个恰有一个元素的集合,那么显然所有二叉树与所有对这 \(m\) 个集合的 \(m-1\) 次合并方式形成双射。那么,一个节点所要统计的子树信息就是所有隶属于该子树的所有叶子节点合并起来的信息。我们可以采用启发式合并来搞这个合并过程,因为它满足「一个节点处的信息量与子树大小同阶」,也就是可以当作集合来合并,并且不会分裂。

更一般的,考察一棵多叉树。它表示一次合并可以同时合并若干个集合。那么启发式合并依然有效。

同时有可能每个节点都有元素,而非仅叶子节点。那么就在合并的时候顺便把这个元素合并进去就可以了,不影响复杂度。

这样子一次 dfs 搞出所有子树的信息值的复杂度是 \(\mathrm O(nT\log n)\),其中 \(T\) 表示往集合里加一个数的复杂度,通常是常数或者 polylog,与 \(n\) 无关的。注意仅当「一个节点处的信息量与子树大小同阶」才可以用启发式合并分析,有的时候即使满足这个条件也可能把复杂度写假掉,变成与 \(n\) 同阶。

然后你想这玩意不就是启发式合并吗,为啥要专门把在树上的应用拎出来独立搞成一个 dsu on tree。这是因为树上启发式合并有普适的简便写法,不需要把每个点处的集合给存下来(其实不仅仅是写法问题,如果非要存下来而非一遍游走一遍修改全局数组的话,会出现空间问题,可能会需要 set 之类的数据结构来强行压空间,可能会多一个 log)。

我们考虑这样一个过程:一路 dfs 励志搞出所有子树的信息值。在每处时,先把所有轻儿子的子树给 dfs 下去,回溯的时候消除影响。最后 dfs 重儿子并且不消除影响。然后把该处单点信息和所有轻儿子的子树的信息都给暴力合并进去,然后填上答案信息值。感性理解一下很容易发现这样和启发式合并的复杂度是一样的。

代码:

void dfs(int x=1,int fa=0){
	for(int i=0;i<nei[x].size();i++){
		int y=nei[x][i];
		if(y==fa||y==wson[x])continue;
		dfs(y,x);消除影响(y,x);
	}
	if(wson[x])dfs(wson[x],x);
	单点合并进去(x);
	for(int i=0;i<nei[x].size();i++){
		int y=nei[x][i];
		if(y==fa||y==wson[x])continue;
		子树合并进去(y,x);
	}
	填答案;
}
posted @ 2021-01-23 12:30  ycx060617  阅读(15)  评论(1编辑  收藏  举报