dsu on tree(树上启发式合并)详解-程序员宅基地

技术标签: 算法小结区  

题外话

据说dsu on tree这个名字十分的离谱,原先它指的是一种代码风格,后来莫名其妙用来指代树上启发式合并这个算法。

以及这个算法和dsu(并查集)更是没有什么关系,这也十分的离谱qwq。

介绍

这是个用来处理这样一类题目的:询问支持离线,并且询问与子树有关。

它可以很方便地在 O ( n l o g n ) O(nlogn) O(nlogn) 的时间内完成。

正题

它是一个利用了重链剖分的一个性质的暴力。

先掏一道模板题来讲吧:

给出一棵节点有颜色的树,每次询问某个节点的子树内有多少种不同的颜色。

我们考虑离线做这个问题,先求出每个节点的答案,然后每次直接 O ( 1 ) O(1) O(1) 回答即可。

考虑对每一个节点求出一个数组,记录每种颜色的出现次数,从而维护出不同颜色的数量,这样做是 n 2 n^2 n2 的。

注意到一个节点的数组可以由儿子们的数组合并得到,由于儿子们求出答案后数组就用不到了,我们就可以直接拿一个儿子的数组来,然后将剩下的儿子的数组合并过来(指暴力合并,一个一个插入)。显然一开始拿的那个数组,拿重儿子的数组是最好的,这样可以减少合并次数。

然后就会惊人地发现,这个暴力时间复杂度竟是优美的 O ( n log ⁡ n ) O(n\log n) O(nlogn)

证明也很简单,利用轻重链剖分的性质,一个点到根路径上不超过 log ⁡ n \log n logn 条轻边,也就是说,一个点最多会被暴力合并 log ⁡ n \log n logn 次。

这个就是树上启发式合并,即每次将轻儿子的信息暴力合并到重儿子的信息上从而得到自己的信息,本质上和一般的启发式合并是一样的:将较小的块合并到较大的块,从而保证每个点至多被合并 log ⁡ n \log n logn 次,因为一个点每次合并后所在的块的大小至少翻倍。

然而实现的时候,我们不需要维护轻儿子的数组,一般我们选择全局只维护一个数组,记录上一个点的信息,然后每次暴力遍历轻儿子的子树将信息合并过来。我们只需要每次先处理了轻儿子,然后清空数组再遍历重儿子,就可以使上一个点的信息成为我们需要的重儿子的信息了。

关于遍历轻儿子子树这个部分,你也可以预处理出dfs序后改成遍历区间,这就是上面所提到的那个dsu on tree代码风格……

代码:

#include <cstdio>
#include <cstring>
#define maxn 100010

int n,m,col[maxn];
struct edge{
    int y,next;};
edge e[maxn*2];
int first[maxn];
void buildroad(int x,int y)
{
    
	static int len=0;
	e[++len]=(edge){
    y,first[x]};
	first[x]=len;
}
int size[maxn],mson[maxn];
void dfs1(int x,int fa)//求重儿子
{
    
	size[x]=1;
	for(int i=first[x];i;i=e[i].next)
	{
    
		int y=e[i].y;
		if(y==fa)continue;
		dfs1(y,x);
		if(size[y]>size[mson[x]])mson[x]=y;
		size[x]+=size[y];
	}
}
int tong[maxn],ans[maxn],now_ans=0;
void go(int x,int fa,int type)
{
    
	tong[col[x]]+=type;
	if(type==1&&tong[col[x]]==1)now_ans++;
	if(type==-1&&tong[col[x]]==0)now_ans--;
	for(int i=first[x];i;i=e[i].next)
	if(e[i].y!=fa)go(e[i].y,x,type);
}
void dfs2(int x,int fa,bool del)
//求解,del表示求完x的子树的答案后需不需要清空x的子树的信息
{
    
	for(int i=first[x];i;i=e[i].next)//先统计轻儿子的答案
	if(e[i].y!=fa&&e[i].y!=mson[x])dfs2(e[i].y,x,true);
	if(mson[x]!=0)dfs2(mson[x],x,false);//最后统计重儿子的答案
	
	tong[col[x]]++;if(tong[col[x]]==1)now_ans++;//统计自己以及轻子树的信息
	for(int i=first[x];i;i=e[i].next)
	if(e[i].y!=fa&&e[i].y!=mson[x])go(e[i].y,x,1);
	ans[x]=now_ans;//得到自己的答案
	
	if(del)go(x,fa,-1);//假如要删掉自己的信息,就暴力地删掉
}

int main()
{
    
	scanf("%d",&n);
	for(int i=1,x,y;i<n;i++)
	scanf("%d %d",&x,&y),buildroad(x,y),buildroad(y,x);
	for(int i=1;i<=n;i++)
	scanf("%d",&col[i]);
	dfs1(1,0);
	dfs2(1,0,false);
	scanf("%d",&m);
	for(int i=1,x;i<=m;i++)
	scanf("%d",&x),printf("%d\n",ans[x]);
}

例题2

题目传送门

还是一颗节点有颜色的树,需要统计每个棵子树内出现次数最多的颜色之和。

板子题,直接上代码:

#include <cstdio>
#include <cstring>
#define maxn 100010

int n,col[maxn];
struct edge{
    int y,next;};
edge e[maxn*2];
int first[maxn];
void buildroad(int x,int y)
{
    
	static int len=0;
	e[++len]=(edge){
    y,first[x]};
	first[x]=len;
}
int size[maxn],mson[maxn];
void dfs_getmson(int x,int fa)
{
    
	size[x]=1;
	for(int i=first[x];i;i=e[i].next)
	{
    
		int y=e[i].y;
		if(y==fa)continue;
		dfs_getmson(y,x);
		if(size[y]>size[mson[x]])mson[x]=y;
		size[x]+=size[y];
	}
}
long long ans[maxn],now_ans=0;
int tong[maxn],max_col=0;
void go(int x,int fa,int type)
{
    
	tong[col[x]]+=type;
	if(type==1)
	{
    
		if(tong[col[x]]>max_col)max_col=tong[col[x]],now_ans=col[x];
		else if(tong[col[x]]==max_col)now_ans+=col[x];
	}
	for(int i=first[x];i;i=e[i].next)
	if(e[i].y!=fa)go(e[i].y,x,type);
}
void dfs_getans(int x,int fa,bool del)
{
    
	for(int i=first[x];i;i=e[i].next)
	if(e[i].y!=fa&&e[i].y!=mson[x])dfs_getans(e[i].y,x,true);
	
	if(mson[x]!=0)dfs_getans(mson[x],x,false);
	for(int i=first[x];i;i=e[i].next)
	if(e[i].y!=fa&&e[i].y!=mson[x])go(e[i].y,x,1);
	
	tong[col[x]]++;
	if(tong[col[x]]>max_col)max_col=tong[col[x]],now_ans=col[x];
	else if(tong[col[x]]==max_col)now_ans+=col[x];
	
	ans[x]=now_ans;if(del)go(x,fa,-1),max_col=0,now_ans=0;
}

int main()
{
    
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d",&col[i]);
	for(int i=1,x,y;i<n;i++)
	scanf("%d %d",&x,&y),buildroad(x,y),buildroad(y,x);
	dfs_getmson(1,0);
	dfs_getans(1,0,false);
	for(int i=1;i<=n;i++)
	printf("%lld ",ans[i]);
}
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/a_forever_dream/article/details/102967706

智能推荐

C# 之 WPF 统计图表开发方案_c# wpflivechart-程序员宅基地

文章浏览阅读9.2k次,点赞8次,收藏72次。C# 之 WPF 统计图表开发文档一、前言二、环境配置1、开发环境2、加载 LiveCharts 库3、添加必须的头文件三、基础图形1、柱状图一、前言本项目的统计图使用LiveCharts 控件集成。LiveCharts, 官网:https://lvcharts.net 是一款简单,灵活,交互式和强大的 DOTNET数据可视化图表控件,内置多种统计图表,可满足本项目的需求。二、环境配置..._c# wpflivechart

DBeaver 快捷键大全_dbeaver快捷键-程序员宅基地

文章浏览阅读5k次,点赞7次,收藏23次。有些快捷键未经验证,如有问题望不吝指正!ctrl + enter 执行sqlctrl + \ 执行sql,保留之前窗口结果ctrl + alt + ↑ 向上复制一行ctrl + alt + ↓ 向下复制一行ctrl + shift + F 对sql语句进行格式化,对于很长的sql语句很有用ctrl + d 删除当前行alt + ↑ 向上选定一条sql语句alt + ↓ 向下选定一条sql语句ctrl + / 行注释ctrl + shift+ / 块注释ctrl + f 查找、替换_dbeaver快捷键

【基础】WebView浏览器组件-程序员宅基地

文章浏览阅读5.4k次。ad_webview

CentOS上面安装Oracle 11GR2_oracle11.2.0需下载什么版本的export-程序员宅基地

文章浏览阅读1.7k次。正常图形化界面安装安装X Windowyum groupinstall "X Window System"yum install unzip.x86_64 vim java-1.8.0-openjdk.x86_64 java-1.8.0-openjdk-devel.x86_64安装依赖软件包yum install binutils compat-libstdc++-33 elfutils-_oracle11.2.0需下载什么版本的export

无中继的DHCP配置-ZTE中兴路由器_中兴1800路由器dhcp-程序员宅基地

文章浏览阅读4.6k次。无中继的DHCP配置DHCP的作用: 动态配置IP地址,整个配置过程自动实现,终端无需设置 所有配置信息统一管理,不仅能够分配IP地址,还可以配置其他信息DHCP的优点: 提高网络配置效率,减少配置工作量,较少IP冲突的可能性DHCP server: 集中存放配置信息,响应客户端的请求与之交互并完成主机配置信息的分..._中兴1800路由器dhcp

计算机网络原理 谢希仁(第8版)第四章习题答案_计算机网络第八版谢希仁课后答案-程序员宅基地

文章浏览阅读9.5w次,点赞368次,收藏1.8k次。第四章网络层习题答案_计算机网络第八版谢希仁课后答案

随便推点

微信小程序-JavaScript 3DES对称加密算法加密使用_js 3des-程序员宅基地

文章浏览阅读8.1k次,点赞6次,收藏22次。一、前言:1. 最近又被领导叫去谈话,公司最近有个二维码模块项目要开发,要求使用微信小程序,说是方面和快捷,不用安装手机APP。o(╥﹏╥)o真是无语,老子在公司的职位是Windwos 开发,现在他们竟然为了省钱,叫我去做微信小程序,碍于今年疫情严重,没有办法,只能重新拾起微信小程序。2. 因公司做的产品为门禁读卡设备,所以一般数据安全性有要求,并且与13.56MhHZ ISO14443A..._js 3des

解决“Windows 安装程序无法配置为在此计算机的硬件上运行”_windows安装程序无法将windows配置为在此计算机上运行-程序员宅基地

文章浏览阅读8.4k次,点赞8次,收藏10次。注:注意:如果这是Windows 10的零售版本,则系统可能还会提示您输入Windows 10的产品密钥。键入 CD x: \windows\system32\oobe (其中 x 是安装了 Windows 的驱动器号,例如 C:\windows\system32\oobe),然后按 Enter 键。键入 cd x: \windows\system32\oobe ( x 是安装 Windows 的驱动器号,例如 c:\windows\system32\oobe),然后按 enter 键。_windows安装程序无法将windows配置为在此计算机上运行

word提示“Word上次启动失败,安全模式可以帮助您解决问题”的解决办法_word上次启动失败,安全模式可以帮助您笔记本电脑-程序员宅基地

文章浏览阅读3.9w次,点赞13次,收藏19次。什么是安全模式?英文Safe Mode;安全模式是Windows操作系统中的一种特殊模式,经常使用电脑的朋友肯定不会感到陌生,在安全模式下用户可以轻松地修复系统的一些错误,起到事半功倍的效果。安全模式的工作原理是在不加载第三方设备驱动程序的情况下启动电脑,使电脑运行在系统最小模式,这样用户就可以方便地检测与修复计算机系统的错误。解决办法1、按下“Win+R”组合键打开运行,..._word上次启动失败,安全模式可以帮助您笔记本电脑

深入理解线程池(详解)_线程池的工作原理-程序员宅基地

文章浏览阅读7.3k次,点赞9次,收藏54次。线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。他的主要特点为:线程复用;控制最大并发数;管理线程。_线程池的工作原理

人工智能:模型与算法——练习题_下面哪一种方法不是通过迭代计算-程序员宅基地

文章浏览阅读6.7k次,点赞9次,收藏71次。第一周 人工智能概述1如果一个问题或者任务不可计算,那么对这个问题或任务的描述哪一句是正确的( ) A.该问题或任务所需计算时间是非线性增加的 B.无法将该问题或任务所需数据一次性装入内存进行计算 C.图灵机不可停机 D.该问题或任务所需计算时间是线性增加的 2下面哪一句话准确描述了摩尔定律( ) A.摩尔定律描述了计算机的计算速度每一年半增长一倍的规律 B.摩尔定律描述了互联网所链接节点随时间不断增长的规律 C.摩尔定律描..._下面哪一种方法不是通过迭代计算

本科生学深度学习,搭建环境,再不入坑就晚了_深度学习为什么要搭建环境-程序员宅基地

文章浏览阅读10w+次,点赞301次,收藏2k次。目录1、目的2、心理准备3、IDE的选择4、AI框架的选择5、安装环境6、总结最近没怎么写游戏了,一直在写python,是因为我对深度学习感兴趣,想学习一下,同时也觉得AI是未来,所以去学习了一段时间。1、目的AI 和游戏的结合是 强化学习,强化学习是深度学习的一个分支,之前也写过一点深度学习,所以这次的学习路线就是由机器学习到深度学习,最后到强化学习,不得不说一条路线对于我来说其实有点难的,深度学习各种公式,各种概念,各种框架,需要时间的积累,所以不是一..._深度学习为什么要搭建环境