树上启发式合并(dsu on tree)学习

news2025/6/9 7:34:31

声明:本文部分内容摘自OI Wiki网站。详情可自行查看学习。

洛谷 P9233

  题目实际上是蓝桥杯 2023 年 A 组省赛的一道题。题干大致的意思是,给定一个含有 n n n 个结点,并且以 1 1 1 为根的一棵树,每个节点 i i i 都有一个颜色 C i C_i Ci,问这棵树有多少个子树是颜色平衡树。其中,如果一棵树中存在的每种颜色的结点个数都相同,则我们称它是一棵颜色平衡树。
  输入:一行一个 n n n,后面的 n n n 行每行一个数对 ( C i , F i ) (C_i,F_i) (Ci,Fi) 分别表示结点 i i i 的颜色和父亲结点。(保证 F 1 = 0 F_1=0 F1=0)。
  输出:这棵树有多少棵子树是颜色平衡树。

题目分析

  感觉题目最大的难点是, C i ≤ 200000 C_i\leq 200000 Ci200000。因为我们最朴素的思想就是:看一棵树是否是颜色平衡树,需要将其子树的颜色情况统计起来。比如,开一个数组c[1..N],其中子树中颜色为i的结点有c[i]个。通过统计一棵树所有子树的c可以得到这棵树的c
  但是现在有一个问题,如果按照上面的思路,显然有 N ≥ C i N\geq C_i NCi;如果每碰到一个子树就开一个c[N],肯定会MLE,并且由于每次合并子树的颜色情况时需要遍历这个数组c,多半也会TLE。
  于是我们又产生了一个想法,用一个map来代替c[N]。因为不是每棵子树都会出现所有的颜色,我们开map不会为没有出现的颜色分配空间,显然空间使用就大大减少了。这种情况下合并子树的颜色情况时,只需要遍历所有子树的map,一一加到树上就行了。
  经过实践,使用map的方法确实不会MLE,但是它TLE了(70 pts)。

  接触到了树上启发式合并这一思想之后,迅速写代码,于是AC。树上启发式合并的核心思想就是保留重儿子。在下面的代码中有注释说明。

AC代码

#include<iostream>

using namespace std;

int n,c[200005],ccnt[200005],cccnt,sz[200005],ncolor[200005],ecnt=1,fedge[200005],ledge[200005],ans,bigchild[200005];
struct {
	int end,next;
}edge[200005];

void buildarc(int begin,int end){
	if(!begin)
		return;
	if(!fedge[begin])
		fedge[begin]=ledge[begin]=ecnt;
	else{
		edge[ledge[begin]].next=ecnt;
		ledge[begin]=ecnt;
	}
	edge[ecnt++].end=end;
}

inline void addcc(int cc){
	if(!ccnt[cc])
		cccnt++;
	ccnt[cc]++;
}
inline void delcc(int cc){
	ccnt[cc]--;
	if(!ccnt[cc])
		cccnt--;
}

void add(int node){
	if(c[ncolor[node]])
		delcc(c[ncolor[node]]);
	addcc(c[ncolor[node]]+1);
	c[ncolor[node]]++;
}
void del(int node){
	c[ncolor[node]]--;
	delcc(c[ncolor[node]]+1);
	if(c[ncolor[node]])
		addcc(c[ncolor[node]]);
}

inline int isbalanced(){return cccnt==1?1:0;}

// dfs0 用来求每个子树的大小的,也就是这个子树有多少个结点。
int dfs0(int cur,int father){
	int childsize,maxchild=0;
	sz[cur]=1;
	for(int e=fedge[cur];e;e=edge[e].next){
		if(edge[e].end==father)
			continue;
		sz[cur]+=(childsize=dfs0(edge[e].end,cur));
		if(childsize>maxchild){
			maxchild=childsize;
			bigchild[cur]=edge[e].end;
		}
	}
	return sz[cur];
}

// 这个是用来加入/删除某一棵树的,mod == 1 是加入,mod == 0 是删除。
void dfs2(int cur,int father,bool mod){//1 add 0 del
	if(mod)
		add(cur);
	else
		del(cur);
	for(int e=fedge[cur];e;e=edge[e].next)
		if(edge[e].end!=father)
			dfs2(edge[e].end,cur,mod);
}
/***
 dfs1 是树上启发式合并的核心算法。一个数有多个子树,其中最大的子树的树根成为这个树的重儿子,其它的儿子是轻儿子。树上启发式算法在每个子树上的操作分为 3 步:
 1.遍历所有轻儿子子树,查看情况,不保留轻儿子子树对原树 c[N] 数组的影响。
 2.查看重儿子子树的情况,并且保留该子树对原树 c[N] 数组的影响。
 3.重新加入所有轻儿子子树,从而得到原树的情况。

@param cur 当前结点
@param father 当前结点的父节点,用 father = 0 表示没有父节点
@param remain 是否要保留 cur 为根的子树对其父树的影响。也即 cur 是否是重儿子。
***/
void dfs1(int cur,int father,bool remain){
	//遍历所有轻儿子
	for(int e=fedge[cur];e;e=edge[e].next)
		if(edge[e].end!=father && edge[e].end!= bigchild[cur])
			dfs1(edge[e].end,cur,false);
	//查看重儿子
	if(bigchild[cur])
		dfs1(bigchild[cur],cur,true);
	//加入根节点
	add(cur);
	//重新加入所有轻儿子子树
	for(int e=fedge[cur];e;e=edge[e].next)
		if(edge[e].end!=father && edge[e].end!= bigchild[cur])
			dfs2(edge[e].end,cur,true);
	ans+=isbalanced();
	//如果要求不保留影响,cur 为根的树的所有贡献
	if(!remain)
		dfs2(cur,father,false);
}

int main(){
	int f;
	cin>>n;
	for(int i=1;i<=n;i++){
		scanf("%d%d",&ncolor[i],&f);
		buildarc(f,i);
	}
	return dfs0(1,0),dfs1(1,0,true),cout<<ans,0;
} 

  AC代码中保留了c[N]数组,其含义与上文所述相同,即颜色的出现次数。可以看到代码中还出现了ccnt数组和cccnt变量。这是一个比较精妙的处理方式,可以在 O ( 1 ) O(1) O(1) 的时间复杂度内判断一棵树是否是颜色平衡树。ccnt[i] = j表示当前共有j个颜色的c值是i,即颜色出现次数的出现次数cccnt颜色出现次数的出现次数的出现次数,当且仅当cccnt == 1的时候,该树是一颗颜色平衡树。
  上面的描述可能有些烧脑,可以用题目给的样例来说明。样例输入:

6
2 0
2 1
1 2
3 3
3 4
1 4

  读者可以根据输入的含义自行画图。对于根节点1而言,颜色1,2,3分别出现了2,2,2次,所以c[1,2,3] = {2,2,2}。根据定义,有3个颜色的出现次数是2,所以ccnt[2] = 3。而对于其它的i != 2,都有cccnt[i] = 0,所以cccnt = 1,因此该树是颜色平衡树。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1592522.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据挖掘】实验7:高级绘图(上)

实验7&#xff1a;高级绘图&#xff08;上&#xff09; 一&#xff1a;实验目的与要求 1&#xff1a;了解R语言中各种图形元素的添加方法&#xff0c;并能够灵活应用这些元素。 2&#xff1a;了解R语言中的各种图形函数&#xff0c;掌握常见图形的绘制方法。 二&#xff1a;实…

软考 - 系统架构设计师 - 面向对象架构设计案例

问题1&#xff1a; 解决该题&#xff0c;用例和参与者要一起进行分析&#xff0c;首先看到用例 U1 和 U2 是 U3 的扩展&#xff0c;分析用例列表中的用例&#xff0c;可以分析出 U1 和 U2 是Underpaid transaction 和 Record lllegal use&#xff0c;顺序可以颠倒&#xff0c;…

QAnything-1.3.0,支持纯python笔记本运行,支持混合检索

QAnything 1.3.0 更新了&#xff0c;这次带来两个主要功能&#xff0c;一个是纯python的安装&#xff0c;另一个是混合检索。更多详情见&#xff1a; https://github.com/netease-youdao/QAnything/releases 纯python安装 我们刚发布qanything开源的时候&#xff0c;希望用户…

rspack 使用构建vue3脚手架

基于 Rust 的高性能 Web 构建工具。rspack 主要适配 webpack 生态&#xff0c;对于绝大多数 webpack 工具库都是支持的。 启动速度快&#xff1b;增量热更新快。兼容 webpack 生态&#xff1b;内置了 ts、jsx、css、css modules 等开箱即用。生产优化&#xff0c;tree shaking…

JVM修炼之路【12】- GC调优 、性能调优

上一篇中 我们详细讲了内存溢出 内存泄漏 还有相关的案例。 这篇博客中我们主要了解一下GC调优。 有些新手可能会有一点 疑问—— 这两者不是一回事吗&#xff1f;&#xff1f; 其实说一回事 也没错 因为GC调优本质上还是针对 堆上的内存 只不过前面我们关注的侧重点在于 不合…

MATLAB绘制地球仪

clc;close all;clear all;warning off;%清除变量% 地球半径&#xff08;单位&#xff1a;千米&#xff09; R 6371;% 定义角度范围 theta linspace(0, 2*pi, 100); % 经度范围 phi linspace(0, pi, 100); % 纬度范围&#xff08;从北极到南极&#xff0c;0到pi&#xff09;%…

thinkphp6入门(23)-- 如何导入excel

1. 安装phpexcel composer require phpoffice/phpexcel composer update 2. 前端 <form class"forms-sample" action"../../xxxx/xxxx/do_import_users" method"post" enctype"multipart/form-data"><div class"cont…

【蓝桥杯】第十五届蓝桥杯大赛软件赛省赛(Java研究生组)个人解题思路及代码分享

文章目录 试题A&#xff1a;劲舞团试题B&#xff1a;召唤数字精灵试题C&#xff1a;封闭图形的个数试题D&#xff1a;商品库存管理试题E&#xff1a;砍柴试题F&#xff1a;回文字符串试题G&#xff1a;最大异或节点试题H&#xff1a;植物生命力 试题A&#xff1a;劲舞团 【问题…

阿里面试总结 一

写了这些还是不够完整&#xff0c;阿里 字节 卷进去加班&#xff01;奥利给 ThreadLocal 线程变量存放在当前线程变量中&#xff0c;线程上下文中&#xff0c;set将变量添加到threadLocals变量中 Thread类中定义了两个ThreadLocalMap类型变量threadLocals、inheritableThrea…

YOLO系列 | 正负样本分配策略

文章目录 1 Max-IoU matching(YOLOv1~V3)2 Multi-Anchor策略(YOLOv4)3 基于宽高比的领域匹配策略(YOLOv5)4 simOTA(Simple Optimal Transport Assignment)匹配策略(YOLOX, YOLOv6)5 领域匹配simOTA(YOLOv7)6 TaskAlignedAssigner匹配策略(YOLOv8, YOLOv9)参考资料 1 Max-IoU ma…

Redis:发布和订阅

文章目录 一、介绍二、发布订阅命令 一、介绍 Redis的发布和订阅功能是一种消息通信模式&#xff0c;发送者&#xff08;pub&#xff09;发送消息&#xff0c;订阅者&#xff08;sub&#xff09;接收消息。这种功能使得消息发送者和接收者不需要直接建立连接&#xff0c;而是通…

STC89C52学习笔记(十一)

STC89C52学习笔记&#xff08;十一&#xff09; 综述&#xff1a;本文讲述了直流电机以及PWM调速。 一、直流电机 1、特点 &#xff08;1&#xff09;直流电机能将电能转化位机械能。 &#xff08;2&#xff09;直流电机有两个电极&#xff0c;电极正接时&#xff0c;电机…

Stable Diffusion文生图技术详解:从零基础到掌握CLIP模型、Unet训练和采样器迭代

文章目录 概要Stable Diffusion 底层结构与原理文本编码器&#xff08;Text Encoder&#xff09;图片生成器&#xff08;Image Generator&#xff09; 那扩散过程发生了什么&#xff1f;stable diffusion 总体架构主要模块分析Unet 网络采样器迭代CLIP 模型 小结 概要 Stable …

WebLogic-XMLDecoder(CVE-2017-10271)反序列化漏洞分析及复现

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java、PHP】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收…

C++string类(个人笔记)

string类 1.认识string的接口以及熟练使用常用接口1.1string类对象的常见构造1.2string类对象的容量操作1.3string类对象的访问及遍历操作1.4string类对象的修改操作 2.vs 和g下string结构的说明3.string类运用的笔试题4.string类的模拟实现 1.认识string的接口以及熟练使用常用…

NPM 命令备忘单

NPM 简介 Node Package Manager (NPM) 是 Node.js 环境中不可或缺的命令行工具&#xff0c;充当包管理器来安装、更新和管理 Node.js 应用程序的库、包和模块。对于每个 Node.js 开发人员来说&#xff0c;无论他们的经验水平如何&#xff0c;它都是一个关键工具。 NPM 的主要…

Day19-【Java SE进阶】网络编程

一、网络编程 1.概述 可以让设备中的程序与网络上其他设备中的程序进行数据交互(实现网络通信的)。java.net,*包下提供了网络编程的解决方案! 基本的通信架构 基本的通信架构有2种形式:CS架构(Client客户端/Server服务端)、BS架构(Browser浏览器/Server服务端)。 网络通信的…

FourCastNet 论文解析

气象基础模型/气象大模型论文速递 论文链接基于arXiv Feb. 22, 2022版本阅读 几乎是第一篇气象大模型的工作&#xff0c;同时也是为数不多的对precipitation进行预测的模型。 文章目录 PerformanceStructureFourier transformToken mixing TrainingPrecipitation Model Ensembl…

Android开发:Camera2+MediaRecorder录制视频后上传到阿里云VOD

文章目录 版权声明前言1.Camera1和Camera2的区别2.为什么选择Camera2&#xff1f; 一、应用Camera2MediaPlayer实现拍摄功能引入所需权限构建UI界面的XMLActivity中的代码部分 二、在上述界面录制结束后点击跳转新的界面进行视频播放构建播放界面部分的XMLActivity的代码上述代…

C++如何排查并发编程死锁问题?

C如何排查并发编程死锁问题&#xff1f; 最近在Apache arrow里面写一个支持并行的算子&#xff1a;nested loop join&#xff0c;然后既然涉及到并行&#xff0c;这里就会遇到大家常说的死锁问题&#xff0c;假设你碰到了死锁问题&#xff0c;如何调试与定位呢&#xff1f; 那这…