dsu on tree 学习笔记

一、概述

\(dsu\ on\ tree\)通常用于解决子树上的问题,要求无修改操作且允许离线。

对于这样的问题,我们以前学过了像树上莫队、点分治等做法,但\(dsu\ on\ tree\)的复杂度远优于他们

虽然\(dsu\ on\ tree\)复杂度优秀,但其实,它就是一个优雅的暴力:

遇到子树问题,最暴力的做法毫无疑问就是暴力枚举子树上的所有点统计答案,实际上\(dsu\ on\ tree\)就是这样做的。

只不过,\(dsu\ on \ tree\)有着优雅的思想:轻重链剖分,它借用对每个点轻儿子与重儿子贡献的分别处理,达到了\(\mathcal O(nlog(n))\)的复杂度。

二、实现

  • 将询问离线,记录在子树的根节点上
  • 遍历整棵树,对于节点\(u\),先计算它轻儿子的答案,计算后删除信息
  • 计算它重儿子的答案,不删除信息
  • 将重子树的信息合并到\(u\)
  • 暴力遍历\(u\)的轻子树,将轻子树的信息合并到\(u\)
  • 处理\(u\)处的询问
  • 根据\(u\)是否是重儿子选择是否删除\(u\)的信息

这就是\(dsu\ on\ tree\)的思想了,大家可能不太理解,我们从一道例题来感受一下:

三、例题

CF600E

题意:

  • 给定一棵\(n\)个节点的以\(1\)为根的树,每个节点都有一个颜色。

  • 如果一种颜色在以\(x\)为根的子树内出现次数最多,称其在以\(x\)为根的子树中占主导地位。显然,同一子树中可能有多种颜色占主导地位。

  • 你的任务是对于每一个\(i \in[1,n]\),求出以\(i\) 为根的子树中,占主导地位的颜色的编号和。

题解:

这是一道经典的\(dsu\ on\ tree\)题目了

考虑如何暴力做:显然可以维护\(w[i]\)表示\(i\)这种颜色出现的次数,同时记录\(ret\)表示目前处理的节点中出现次数最多的颜色的编号和。

当遍历到\(u\)时,首先我们枚举它的轻儿子递归下去,遍历轻儿子后,要删除轻子树节点对于\(w\)的影响

接着遍历重子树,这次我们保留这些节点的贡献

那么全局变量中已经保存了重子树的信息了,轻子树的信息我们直接暴力枚举,修改\(w\)\(ret\)

遍历完后,该节点的答案就是\(u\)的答案,最后,删除该节点的贡献,也是暴力枚举它的子树中的所有节点并删去。

代码如下:

int hson[N],siz[N],w[N],mx,son;
ll ret,ans[N];
inline void dfs(int u,int f){
	siz[u]=1;
	for(int i=first[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==f) continue;
		dfs(v,u);
		siz[u]+=siz[v];if(siz[v]>siz[hson[u]]) hson[u]=v;
	}//轻重链剖分模板
}
inline void work(int u,int f,int tp){
	w[col[u]]+=tp;//tp=1表示要增加这个节点的贡献,-1则是减去该节点的贡献
	if(w[col[u]]>mx) mx=w[col[u]],ret=col[u];
	else if(w[col[u]]==mx) ret+=col[u];//更新ret
	for(int i=first[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==f||v==son) continue;//son保存的是重儿子,不要遍历到重儿子去
		work(v,u,tp);
	}
}
inline void dsu(int u,int f,int tp){//tp=1表示不删除信息,tp=0表示要删除
	for(int i=first[u];i;i=e[i].nxt){
		int v=e[i].v;
		if(v==f||v==hson[u]) continue;
		dsu(v,u,0); //处理轻儿子,要删除信息
	}
	if(hson[u]) dsu(hson[u],u,1),son=hson[u];//遍历重儿子,不删除信息
	work(u,f,1);//暴力遍历轻子树
    son=0;//接下来删除贡献是暴力遍历整个子树而不仅是轻子树了
	ans[u]=ret;
	if(!tp) work(u,f,-1),mx=0,ret=0;//直接删除所有节点的贡献
}

看起来十分暴力吧?它的复杂度其实确实是\(\mathcal O(nlog(n))\)的,这里给出了粗略的证明:

首先,根据轻重链剖分的性质,每一个点到根的路径上至多有\(\mathcal O(log(n))\)条轻边。

考虑一个点\(u\)在什么时候会被遍历到:

\(u\)被一个祖先节点\(x\)统计当且仅当\(u\)\(x\)的轻子树上,也就是说\(x-u\)的这条链第一条边是轻边,那么唯一一条轻边对应唯一一个\(x\),所以至多被统计\(\mathcal O(log(n))\)

\(u\)被一个祖先节点遍历以删除贡献当且仅当\(x\)是一个轻儿子,在\(u\)的祖先中,轻儿子的数量不超过\(\mathcal O(log(n))\)个,于是也只会被遍历\(\mathcal O(log(n))\)

综上,因为每个点的信息修改是\(\mathcal O(1)\)的,所以总复杂度是\(\mathcal O(nlog(n))\)

四、更多例题

posted @ 2021-01-09 17:39  cjTQX  阅读(95)  评论(0编辑  收藏  举报