CodeForces 600E. Lomsat gelral【树上启发式合并】

传送门

好像大家都是拿这道题作为树上启发式合并的板子题。

树上启发式合并,英文是 dsu on tree,感觉还是中文的说法更准确,因为这个算法和并查集(dsu)没有任何关系。一般用来求解有根树的所有子树的统计问题。

根据轻重儿子的各种性质,可以证明这个算法的时间复杂度为 \(O(nlogn)\),虽然看起来暴力的不行,但是却是一个很高效的算法。

算法的核心其实就是对于每个节点,先计算轻儿子,再计算重儿子,把自己和轻儿子的所有贡献累计到重儿子上去,如果自己是轻儿子,就把贡献清空。

如果掌握了树链剖分和点分治,理解这个算法的流程还算挺简单的。

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <queue>
#define xx first
#define yy second
using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
const int inf=0x3f3f3f3f;
const LL INF=0x3f3f3f3f3f3f3f3f;
const int N=3e5+10;
const int M=1e6+10;
int n,clr[N],siz[N],son[N],Son;
LL sum,maxc,cnt[N],ans[N];
vector<int> g[N];

void predfs(int u,int fa){
	siz[u]=1;
	for(int v:g[u]){
		if(v==fa) continue;
		predfs(v,u);
		siz[u]+=siz[v];
		if(siz[v]>siz[son[u]]) son[u]=v;
	}
}

void calc(int u,int fa,int val){
	cnt[clr[u]]+=val;
	if(cnt[clr[u]]>maxc) maxc=cnt[clr[u]],sum=clr[u];
	else if(cnt[clr[u]]==maxc) sum+=clr[u];
	for(int v:g[u]){
		if(v==fa||v==Son) continue;
		calc(v,u,val);
	}
}

void dfs(int u,int fa,bool keep){
	//计算轻儿子
	for(int v:g[u]){
		if(v==fa||v==son[u]) continue;
		dfs(v,u,false);
	}
	//计算重儿子
	if(son[u]) dfs(son[u],u,true),Son=son[u];
	//计算自己这颗子树的答案
	calc(u,fa,1);
	ans[u]=sum;
	Son=0;
	//如果自己是轻儿子的话,就是清空自己对父亲的贡献
	if(!keep) calc(u,fa,-1),sum=maxc=0;
}

int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++) scanf("%d",&clr[i]);
	for(int i=1,u,v;i<n;i++){
		scanf("%d%d",&u,&v);
		g[u].push_back(v);
		g[v].push_back(u);
	}
	predfs(1,1);
	dfs(1,1,1);
	for(int i=1;i<=n;i++) printf("%lld ",ans[i]);
	return 0;
}

原文地址:https://www.cnblogs.com/BakaCirno/p/12604147.html

时间: 2024-10-12 10:00:35

CodeForces 600E. Lomsat gelral【树上启发式合并】的相关文章

【CF600E】Lomsat gelral——树上启发式合并

(题面来自luogu) 题意翻译 一棵树有n个结点,每个结点都是一种颜色,每个颜色有一个编号,求树中每个子树的最多的颜色编号的和. ci <= n <= 1e5 树上启发式合并裸题.统计时先扫一遍得到出现次数最大值,然后再扫一遍看哪个颜色的出现次数与mxCnt相等.注意用一个bool数组判重,清空轻儿子贡献时要顺手把bool数组也打成false. 代码: #include <iostream> #include <cstdio> #include <cctype&

codeforces 600E . Lomsat gelral (线段树合并)

You are given a rooted tree with root in vertex 1. Each vertex is coloured in some colour. Let's call colour c dominating in the subtree of vertex v if there are no other colours that appear in the subtree of vertex v more times than colour c. So it'

CF 600 E Lomsat gelral —— 树上启发式合并

题目:http://codeforces.com/contest/600/problem/E 看博客:https://blog.csdn.net/blue_kid/article/details/82192641 https://blog.csdn.net/clove_unique/article/details/60772212 还是不太明白复杂度... 代码如下: #include<iostream> #include<cstdio> #include<cstring&g

Codeforces 600E. Lomsat gelral(Dsu on tree学习)

题目链接:http://codeforces.com/problemset/problem/600/E n个点的有根树,以1为根,每个点有一种颜色.我们称一种颜色占领了一个子树当且仅当没有其他颜色在这个子树中出现得比它多.求占领每个子树的所有颜色之和. 我们都知道可以$BST$启发式合并从而完美${O(nlogn^{2})}$,这太丑陋了. 那么$Dsu~~on~~tree$是在干啥呢? 找出树中每一个节点的重儿子,统计答案的时候优先进入每一个点的所有轻儿子,之后再进入重儿子,目的是保留重儿子所

Codeforces 600E Lomsat gelral (Dsu On the Tree)

题目链接 Lomsat gelral 占坑--等深入理解了再来补题解-- 1 #include <bits/stdc++.h> 2 3 using namespace std; 4 5 #define rep(i, a, b) for (int i(a); i <= (b); ++i) 6 7 typedef long long LL; 8 9 const int N = 600010; 10 11 int n; 12 int cc[N], col[N], sz[N], son[N];

CodeForces 600E Lomsat gelral(线段树合并)

题目链接:http://codeforces.com/problemset/problem/600/E You are given a rooted tree with root in vertex 1. Each vertex is coloured in some colour. Let's call colour c dominating in the subtree of vertex v if there are no other colours that appear in the

Codeforces.600E.Lomsat gelral(dsu on tree)

题目链接 dsu on tree参见这. \(Description\) 给定一棵树.求以每个点为根的子树中,出现次数最多的颜色的和. \(Solution\) dsu on tree模板题. 用sum[i]表示出现次数为i的颜色的和,cnt[i]表示出现次数为i的颜色有多少个(其实有个Max表示当前最多的次数,和tm[i]就好了),然后就这样了.. 再写一遍dsu on tree大体过程:(设当前点为x) 计算轻儿子子树的答案,并删掉轻儿子的贡献(大多数时候): 计算重儿子子树的答案,保留重儿

Codeforces 600E - Lomsat gelral 「$Dsu \ on \ tree$模板」

With $Dsu \ on \ tree$ we can answer queries of this type: How many vertices in the subtree of vertex $v$ has some property in $O (n \log n)$ time (for all of the queries)? 这题写的是轻重儿子(重链剖分)版本的 $Dsu \ on \ tree$ 具体流程如下: 每次先递归计算轻儿子,再单独递归重儿子,计算完后轻儿子的一些信息

CodeForces 375D. Tree and Queries【树上启发式合并】

传送门 题意 给出一棵 \(n\) 个结点的树,每个结点有一个颜色 \(c_i\) . 询问 \(q\) 次,每次询问以 \(v\) 结点为根的子树中,出现次数 \(\ge k\) 的颜色有多少种.树的根节点是 \(1\). 题解 反正我看见这个 \(\ge k\) 就觉得要用线段树,实际上好像不用写线段树的 Orz. 还是树上启发式合并,记录每种颜色出现的次数,然后线段树记录某种次数有多少颜色,更改就在线段树上改. 这是最后一道树上启发式合并的例题了,以后遇到再刷. #include <bit