题目链接:CF600E
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’s possible that two or more colours will be dominating in the subtree of some vertex.
The subtree of vertex v is the vertex v and all other vertices that contains vertex v in each path to the root.
For each vertex v find the sum of all dominating colours in the subtree of vertex v.
Input
The first line contains integer n (1 ≤ n ≤ 105) — the number of vertices in the tree.
The second line contains n integers ci (1 ≤ ci ≤ n), ci — the colour of the i-th vertex.
Each of the next n - 1 lines contains two integers xj, yj (1 ≤ xj, yj ≤ n) — the edge of the tree. The first vertex is the root of the tree.
Output
Print n integers — the sums of dominating colours for each vertex.
Examples
inputCopy
4
1 2 3 4
1 2
2 3
2 4
outputCopy
10 9 3 4
inputCopy
15
1 2 3 1 2 3 3 1 1 3 2 2 1 2 3
1 2
1 3
1 4
1 14
1 15
2 5
2 6
2 7
3 8
3 9
3 10
4 11
4 12
4 13
outputCopy
6 5 4 3 2 3 3 1 1 3 2 2 1 2 3
题意:有一颗n个节点的树,每个节点都被染上了一种颜色,现在让你找每个子树被什么颜色所占据,子树被一种颜色所占据的意思就是:子树中不存在有另外一种颜色出现的次数比占据子树的颜色的出现次数多。所以占据子树的颜色可能有多个,因为可能多个颜色出现次数相等,现在要你求每个子树被什么颜色所占据。
首先,总结一下树上启发式合并算法的具体步骤:
摘自:[洛谷日报第65期]树上启发式合并
前提:树链剖分,将节点划分为重儿子和轻儿子
Q:什么是重儿子?
A:重儿子就是树的左右节点中,拥有子节点多的那个节点
代码:
void getSon(int u,int fa){
Size[u]=1;
for(int t=head[u];t!=-1;t=edge[t].next){
int to=edge[t].to;
if(to!=fa){
getSon(to,u);
Size[u]+=Size[to];
if(Size[to]>Size[son[u]])
son[u]=to;
}
}
}
定义 ans数组为节点的答案,cnt数组为节点颜色出现的次数
启发式合并关键三步:
第一步:遍历所有轻儿子,获取它们的ans,但不保留遍历后它们的cnt
第二步:遍历所有重儿子,保留它们的cnt
第三步:再次遍历轻儿子极其父亲,用重儿子的cnt对遍历的节点进行计算,获取整颗子树的ans
PS:具体看代码
知道这些之后,做这个题目就没有什么难度了。
代码:
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define ULL unsigned long long
#define LL long long
#define Max 1000005
#define mem(a,b) memset(a,b,sizeof(a));
#define pb push_back
#define mp make_pair
#define input ios::sync_with_stdio(false);cin.tie(0);
#define debug printf("debug!!!\n");
const LL mod=1e9+7;
const ULL base=131;
const LL LL_MAX=9223372036854775807;
using namespace std;
struct node{
int to,next;
}edge[Max];
int tot;
int cnt[Max],head[Max],son[Max],Size[Max],c[Max];
/*cnt记录节点颜色出现次数
head保存邻接表头指针
son 保存节点重儿子
size保存节点子节点个数
c保存节点颜色*/
LL ans[Max];
int Son,mx;
LL sum;
inline void init(){
tot=0;
mem(head,-1);
}//邻接表初始化
inline void addedge(int u,int v){
edge[tot].to=v;
edge[tot].next=head[u];
head[u]=tot++;
}//链接表加边
void getSon(int u,int fa){//得到重儿子
Size[u]=1;
for(int t=head[u];t!=-1;t=edge[t].next){
int to=edge[t].to;
if(to!=fa){
getSon(to,u);
Size[u]+=Size[to];
if(Size[to]>Size[son[u]])
son[u]=to;
}
}
}
void add(int u,int fa,int val){
cnt[c[u]]+=val;
if(cnt[c[u]]>mx)
sum=c[u],mx=cnt[c[u]];
else if(cnt[c[u]]==mx)
sum+=c[u];
for(int t=head[u];t!=-1;t=edge[t].next){
int to=edge[t].to;
if(to!=fa && to!=Son){
add(to,u,val);
}
}
}
int dfs(int u,int fa,int keep){//keep代表需不需要保留cnt
for(int t=head[u];t!=-1;t=edge[t].next){
int to=edge[t].to;
if(to!=fa && to!=son[u]){//首先遍历所有轻儿子
dfs(to,u,0);//不保留cnt,keep=0
}
}
if(son[u])
dfs(son[u],u,1),Son=son[u];//保留cnt,keep=1
add(u,fa,1);Son=0;//计算ans
ans[u]=sum;
if(!keep)//如果不保留cnt,只要重新添加一个-1给cnt就可以了
add(u,fa,-1),sum=0,mx=0;
}
int main()
{
init();
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&c[i]);
for(int i=1;i<n;i++){
int x,y;
scanf("%d%d",&x,&y);
addedge(x,y);
addedge(y,x);
}
getSon(1,0);
dfs(1,0,0);
for(int i=1;i<=n;i++)
printf("%lld ",ans[i]);
printf("\n");
return 0;
}