树上启发式合并 洛谷U41492

洛谷U41492 树上数颜色 树上启发式合并 题目链接:https://www.luogu.com.cn/problem/U41492 题意:给一棵根为1的树,每次询问子树颜色种类数 思路:树上启发式合并,每次计算时如果是第一次出现就计算贡献,轻链的话要消除贡献 #include<bits/stdc++.h> using namespace std; typedef long long ll; #define fi first #define se second #define ls rt << 1 #define rs . 阅读详情

对于子树类不修改的问题,比如说统计所有子树的颜色种类数,以下面这个样例为例子:

要统计以u为根的子树,应该暴力统计u的各个子树之和。流程应该是这样:假设规模为一个三个结点的二叉树,u为根,v1,v2为两个叶子节点,暴力统计以v1为根的节点,然后清空统计数组,再暴力统计以v2为根的节点,然后再清空数组,统计u为根的子树。我们可以发现,是因为两棵子树一定毫不相干我们才清空,但是v2与u不是毫不相干的,他们是包含关系的,所以我们可以保留v2的,那么在统计u的时候就可以不统计v2的了。更一般地,应该是保留u的最后一个儿子,节省统计u的时间,为了最大化节省的时间,我们就把重儿子放在最后一个(因为重儿子节点数最多,最耗时间),所以实际算法为了确保轻儿子一定在统计的前面,我们就一路轻儿子递归至叶子,再自底向根的回溯式统计。

统计代码:

void get_light_son(int u,int hson,int fa,int k)//统计轻儿子
{
	cnt[color[u]]+=k;
	if(k>0&&cnt[color[u]]==1) tot++;//一定要有k>0,因为可以从0->1也可以2->1,会多计数,只统计增加时的1
	if(k<0&&cnt[color[u]]==0) tot--;//同上
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if((k==1&&v==hson)||v==fa) continue;//如果v是fa或者是在增加的时候v是重儿子(被保留在cnt数组了,不需要再记录),跳过
		get_light_son(v,hson,u,k);//递归的时候hson还是hson,因为我们只是不统计根的重儿子,即一开始传入的hson
	}
}

void dfs(int u,int fa)
{
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v!=fa&&v!=son[u]) dfs(v,u); //一路遍历轻儿子到叶子
	}
	if(son[u]) dfs(son[u],u);//重儿子放在最后统计
	get_light_son(u,son[u],fa,1);//计数这个节点为根的子树的所有节点,因为这个节点的重儿子被最后留下在cnt数组了,所以在内部特判重儿子,不记录
	ans[u]=tot;//以u为根的子树就是原先保留的重儿子加上get_light_son()的轻儿子记录,tot记录种数
	if(u!=son[fa]) get_light_son(u,son[u],fa,-1);//这个节点不是fa的重儿子,直接清空记录
}

其余就是链式前向星和统计树链剖分求重儿子的常规操作。

树上启发式合并的复杂度是O(nlogn)的。证明待补。

完整代码:

#include<iostream>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<cctype>
#include<algorithm>
#include<string>
#include<vector>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<deque>
#define ll long long
#define ull unsigned long long
#define fr first
#define se second
#define endl '\n'
using namespace std;

const int inf=0x3fffffff;
const long long INF=0x3f3f3f3f3f3f;
const double eps=1e-8;
const double pi=2*acos(0.0);

template<typename T>
T father(T x){return x&1?x-1>>1:x>>1;}
template<typename T>
T ls(T x){return x<<1;}
template<typename T>
T rs(T x){return x<<1|1;}
template<typename T>
T abs_(T a){return a<0?-a:a;}
template<typename T>
T ceil_(T a,T b){return a%b?a/b+1:a/b;}
template<typename T>
T log_(T a,T b){return log(b)/log(a);}
template<typename T>
T gcd(T a,T b){return !b?a:gcd(b,a%b);}
template<typename T>
T lcm(T a,T b){return a/gcd(a,b)*b;}
template<typename T>
void exgcd(T a,T b,T& x,T& y)
{
	if(!b){x=1;y=0;return;}
	exgcd(b,a%b,x,y);
	T temp=x;x=y;
	y=temp-a/b*y;
}
template<typename T>
T solve_function(T a,T b,T c)
{
	if(a<0){a=-a;c=-c;}
	T x,y,base=gcd(a,b);
	exgcd(a,b,x,y);
	x*=c/base;T p=b/base;
	x=(x%p+p)%p;
	if(x<0) x+=abs_(p);
	return x;
}
template<typename T>
T reverse_element(T a,T k){return solve_function(a,k,(T)1);}
template<typename T>
T slow_multi(T a,T b,T p)
{
	T ret=0;
	while(b)
	{
		if(b&1) ret=(ret+a)%p;
		a=(a<<1)%p;
		b>>=1;
	}
	return ret;
}
template<typename T>
T qpow(T a,T b)
{
	T ret=1,base=a;
	while(b)
	{
		if(b&1) ret=ret*base;
		base=base*base;
		b>>=1;
	}
	return ret;
}

int read()
{
	int ret=0,base=1;
	char ch=getchar();
	while(!isdigit(ch))
	{
		if(ch=='-') base=-1;
		ch=getchar();
	}
	while(isdigit(ch))
	{
		ret=(ret<<1)+(ret<<3)+ch-48;
		ch=getchar();
	}
	return ret*base;
}

struct way
{
	int to,next;
}edge[200005];
int cnt2,head[100005];

void add(int u,int v)
{
	edge[++cnt2].to=v;
	edge[cnt2].next=head[u];
	head[u]=cnt2;
}

void add_(int u,int v){add(u,v);add(v,u);}

int n,color[100005],sum[100005],son[100005];
int maxCnt,tot,cnt[100005],ans[100005];

void dfs1(int u,int fa)
{
	sum[u]=1;
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v==fa) continue;
		dfs1(v,u);
		sum[u]+=sum[v];
		if(sum[v]>sum[son[u]]) son[u]=v;
	}
}

void get_light_son(int u,int hson,int fa,int k)//统计轻儿子
{
	cnt[color[u]]+=k;
	if(k>0&&cnt[color[u]]==1) tot++;
	if(k<0&&cnt[color[u]]==0) tot--;
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if((k==1&&v==hson)||v==fa) continue;
		get_light_son(v,hson,u,k);
	}
}

void dfs(int u,int fa)
{
	for(int i=head[u];i;i=edge[i].next)
	{
		int v=edge[i].to;
		if(v!=fa&&v!=son[u]) dfs(v,u); 
	}
	if(son[u]) dfs(son[u],u);
	get_light_son(u,son[u],fa,1);
	ans[u]=tot;
	if(u!=son[fa]) get_light_son(u,son[u],fa,-1);
}

int main()
{
	n=read();
	for(int i=1;i<n;i++) add_(read(),read());
	for(int i=1;i<=n;i++) color[i]=read();
	dfs1(1,0);dfs(1,0);
	for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
 	return 0;
}

启发式合并、树上启发式合并(基础) 启发式合并 阅读详情

相关推荐

从VoLTE高清通话到5G新通话:手把手带你用Wireshark抓包分析QCI 1, 2, 5, 9的建立与切换过程

本文详细解析了从VoLTE高清通话到5G新通话的QCI(QoS Class Identifier)建立与切换过程,通过Wireshark抓包实战演示如何分析QCI 1、2、5、9的承载机制。内容涵盖QCI基础概念、实验环境搭建、VoLTE通话建立过程分析以及5G新通话中的QoS演进,为网络工程师和通信技术爱好者提供实用的技术指南和优化建议。

weixin_30788619的博客 342

树上启发式合并/dsu on tree的简单应用

dsu on tree,树上启发式合并,轻重链剖分,树上数颜色,CF600E,赛氪2021冬季赛,树的果实,CF1709E

深海鱼!的博客 324

overleaf导入模板

本文介绍了如何在Overleaf平台导入Springer Nature模板。首先下载所需的模板压缩包,然后在Overleaf中选择"Upload project"上传该压缩包。上传后会显示模板文件,包括EPS格式图像文件、LaTeX源文件、BibTeX参考文献文件、PDF输出文件以及文档类文件。这些文件共同构成了完整的LaTeX模板,可用于学术论文写作和排版。

A2787960542的博客 1148

启发式合并

我觉得启发式合并真的很需要策略,巨大的思维量。启发式合并,就是用并查集合并两个节点时,根据节点携带信息的大小siz,来决定合并方向。从而使得合并时遍历节点信息的复杂度稳定在Onlogn左右。一般启发式合并的难点还是在于那个并查集指代的信息,要维护节点的什么信息,合并时是否要将信息拎出来计算答案等……总之很有思维量,会很难调,但是时间复杂度很优秀。

m0_60506105的博客 974

描述符链表(Descriptor Chain)是什么?

描述符链表是硬件加速数据传输的核心数据结构,通过链式描述符(包含地址、长度、控制标志等)实现DMA自动批量处理,显著降低CPU负载。其工作原理为:CPU初始化链表后,硬件按序处理每个描述符,完成自动跳转与中断通知。在网卡收包、NVMe存储等场景中应用广泛,优势包括批处理、低延迟和灵活调度。需注意内存对齐、缓存一致性等实现细节,优化手段包括预分配描述符池、环形缓冲区等。该技术是网络/存储领域提升吞吐量的关键方案。

guigenyi的专栏 773

新手必看!Stata工具变量分析中的三大陷阱:如何正确解读LM统计量、CDW检验和Hansen J值

本文深度解析Stata工具变量分析中的三大核心检验:不可识别检验、弱IV检验和过度识别检验,帮助新手避开常见陷阱。详细解读LM统计量、CDW检验和Hansen J值的正确使用方法,提供实战案例和Stata操作指南,确保研究结论的可靠性。

weixin_29231027的博客 571

【Luogu U41492】树上数颜色——树上启发式合并(dsu on tree)

(这题在洛谷主站居然搜不到……还是在百度上偶然看到的) 题目描述 给一棵根为1的树,每次询问子树颜色种类数 输入输出格式 输入格式: 第一行一个整数n,表示树的结点数 接下来n-1行,每行一条边 接下来一行n个数,表示每个结点的颜色c[i] 接下来一个数m,表示询问数 接下来m行表示询问的子树 输出格式: 对于每个询问,输出该子树颜色数 说明 对于前三组数据,1...

weixin_30274627的博客 636

MAC-电脑中的user文件夹在哪?

MAC-电脑中的user文件夹在哪?

兴趣是最好的老师 1万+

树上启发式合并总结

前言 某一天发现一道树上启发式合并裸题,但我不会写…… 学习并刷了两天的题,是时候来写个总结了 正文 树上启发式合并(DSU on Tree),是一个在O(nlogn)O(nlogn)O(nlogn)时间内解决许多树上问题的有力算法。 但它的中心其实是——暴力! 没错,它正是由暴力优化而来。 我们先看一道例题:CF600E Lomsat gelral 题意简述:一棵树有n个结点,每个结点都是一种颜...

p_b_p_b的博客 6242

Open3D 计算点云配准的旋转和平移误差

通过计算旋转误差和平移误差来量化配准结果与真实变换之间的偏差。

点云侠的博客 250

基于 51 单片机的智能手势控制小车设计与实现

智能手势控制小车以STC89C52RC 单片机为控制核心,通过手势识别模块采集用户手势指令,经单片机处理后驱动电机执行前进、后退、左转、右转等动作。该项目融合了传感器技术、单片机编程和电机驱动技术,具有成本低、易实现、互动性强的特点,适合作为嵌入式系统入门实践案例。本项目成功实现了基于 51 单片机的智能手势控制小车,通过 APDS-9960 模块采集手势信号,经单片机处理后驱动电机完成基本动作,验证了 “传感器 + 单片机 + 执行器” 的嵌入式系统设计思路。

weixin_42300449的博客 177

洛谷U41492(树上启发式合并)

提交通道 洛谷日报 考虑非\(O(n^2)\)的预处理。一遍dfs时,check某颜色有没有的数组何时清空很尴尬:得到某树答案后如果不清,则影响接下来兄弟树的搜索;如果清了,父亲节点又难以收集答案。 解决方法:先让儿子们各顾各的家,算一遍各自的答案(假如能算),check清就清了吧。然后考虑人为优化,即重链求完后等一等!先别清!然后将轻链重新扫一遍,也不清check数组的。代码中的kee...

278

Unity3D实现项目限制功能(使用次数限制和时间限制)

大家好,我是心疼你的一切,不定时更新Unity开发技巧,觉得有用记得一键三连哦。在软件开发的时候,可能会遇到程序的使用次数限制,以及时间的限制,下面就写一下这两种方法是新建注册表,增加键值对,修改键值,完成对程序的控制提示:以下是本篇文章正文内容,下面案例可供参考以上就是讲了两种限制方法不定时更新Unity开发技巧,觉得有用记得一键三连哦。

心疼你的一切博客 1810

树上启发式合并

这只是人们的基于直觉,一般来说树越大往往越高,那么小树优先合并到大树上面,似乎可以使得树高较低一点,是一种启发式策略,所以称为。暴力思路:对每一个子节点进行一次遍历,每次遍历是O(N)的,因此总体时间复杂度O(N^2)处理完所有的轻儿子之后,对于重儿子我们根据欧拉序,累加整棵子树的颜色——O(N),并且保留。外,还有类似于树链剖分的做法,我们也做 轻重儿子 的划分,这样做事实上有很优秀的性质。轻儿子dfs计算答案,即dfs轻儿子后,轻儿子子树内的贡献不保存在 cnt[] 数组。轻儿子:除了重儿子之外的。

EQUINOX1的博客 852

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

U41492 数颜色 树上启发式合并

树上启发式合并的经典问题。 首先离线处理答案。用一个颜色数组记录某种颜色是否存在过,然后由于不同子树统计答案的时候互不影响,我们枚举子树的时候要清空之前的数组,但是这就带来了一个问题,我们无法向上合并答案了。 启发式合并的做法是我们先考虑树剖预处理,然后对于统计一个结点的答案,先暴力递归所有轻儿子的答案不保留数组,然后递归重儿子并且保留数组,然后再次递归所有的轻儿子这次也保留数组,从而向上更新答案...

qq_40859782的博客 212

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

U41492 树上数颜色(dsu)

U41492 树上数颜色 题意: 输入n(1e5)n(1e5)n(1e5)表示一棵根为1的树有nnn个节点 接下来n−1n-1n−1行每行u,vu,vu,v表示树边 接下来一行nnn个数,c1,c2,…,cn(1≤ci≤n)c_1,c_2,\dots,c_n(1\leq c_i\leq n)c1​,c2​,…,cn​(1≤ci​≤n)表示节点颜色 接下来m(m≤n)m(m\leq n)m(m≤n)...

IQ^QI的博客 477

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

【题目整理】树上启发式合并dsu on tree

真是可爱啊〜 ——《辉夜大小姐想让我告白》四宫辉夜

xhyu61的博客 259

【算法讲20:Dsu on Tree】树上数颜色 | Lomsat gelral

【算法讲20:Dsu on Tree】【例一:树上数颜色】· 暴力做法 ·· 考虑优化 ·⌈Dsu on Tree⌋\lceil Dsu\ on\ Tree\rfloor⌈Dsu on Tree⌋【代码】【例二:Lomsat gelralLomsat\ gelralLomsat gelral】 【例一:树上数颜色】 【链接】 树上数颜色 | 洛谷 U41492 【题意】 给定一棵有根树,有 nnn 个节点,每个节点有一个颜色 cic_ici​

溢流眼泪的博客 961

洛谷U41492 树上数颜色【dsu on tree】

dsu on tree

juruo_c的博客 553

20251208树上启发式合并总结

树上启发式合并在处理离线问题时,其效率往往优于或等同于其他算法,同时具备更容易理解和实现的优势。传统解决方案通常依赖复杂的数据结构,但对于可离线处理的问题,是否存在更简便的方法?,即对每个子节点执行一次遍历(每次遍历复杂度与n同阶),n个节点导致总复杂度为。注意到每个节点的答案可由其子树及自身信息推导得出,这一特性可用于优化处理。启发式合并算法通过将高度较小的树作为高度较大树的子树进行优化。离线处理的核心在于通过预处理实现。直接暴力预处理的时间复杂度为。时间复杂度的查询输出。

scx20131004的博客 247

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

不妨这样做: 每往上传递子树的贡献时, 其他 ( 轻子树 )的仍旧暴力枚举所有点来计算贡献. 最大的那个子树 ( 重子树) 留到最后一个跑 dfs, 此时跑出来的结果不再动他, 直接往上回溯, 就传承给了上一级. 形象地说, 就是所有轻子树合并到了重子树上, 作为 fa 的子树的贡献.就是说: 遍历所有子树的贡献时, 由于换一根子树 dfs 之前就要清除之前子树的贡献, 我们可以选择一个子树作为最后跑 dfs 的, 这样他就是不用清除的, 就可以节省复杂度.可以证明这样的复杂度是。

taulee01 的博客 534

树上启发式合并 学习记录

树上启发式合并

bawangtianzun的博客 255

U41492 dsu on tree

题意 传送门 U41492 树上数颜色 题解 预处理各子树的答案,朴素的方法即对每个节点向下 DFSDFSDFS 一次,时间复杂度 O(N2)O(N^2)O(N2)。考虑到节点的答案为子树信息的叠加,使用树上启发式合并算法。 基本思想是递归求解儿子的答案,将重儿子(即节点数最多的儿子)放到最后求解,且不清空它所记录的信息,此时可以保留的信息是最多的;那么求解当前节点的答案时,只需要再统计一次非重儿子的信息。 定义连向重儿子的为重边,其余为轻边。由于轻边连接的子树节点个数不超过父节点所在子树的节点个数的一半,

neweryyy的博客 173

luogu U41492 树上数颜色 (dsu on tree 或 主席树+dfn序)

这种题还考的挺多的(口胡),我稍微总结了一下,不多,就两个题目 其中一个就是luogu U41492 树上数颜色 另外一个是东北四省E题算是这题的加强版 此处提供两种算法:1.启发式合并 2.主席树 关于启发式合并,大家可以看看这片博客https://www.luogu.org/blog/codesonic/dsu-on-tree 代码略微相似: #include&lt...

weixin_43824564的博客 932

树上启发式合并 待补

对于每个子树,直接遍历所有轻儿子,继承重儿子。会了板子后,修改维护的东西和莫队是一样的。板子,改一下修改操作就行了。

qq_41418557的博客 335

dsu on tree 模板 (树上启发式合并)

题目 题目 题意: T1:给定一棵n个点的树,求每个点以它为树根的子树的颜色种类。 T2:给定一棵n个点的树,求每个点以它为树根的子树的dominating color之和。 dominating color: 在本棵树中出现次数最多(或之一) 思路: dsu on tree,树上启发式合并,不过这个算法并没有用到dsu(并查集)。 考虑暴力求解,以每个点为起点dfs搜索一次,时间复杂度为n^2。 但是可以利用轻重链的性质,优化为nlogn。   注意到一个点的贡献是所有儿子求和,求完和儿子的贡献就用不到了

xianqiuyigedao的博客 349
上一篇: Codeforces 1696D Permutation Graph
下一篇: Codeforces 707E 二维树状数组
stdforces
博客等级 码龄4年 9粉丝 136原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值