CF600E Tree Requests(树上启发式合并模板题)

一道关于树上启发式合并的模板题CF600E,要求找出每棵子树的主导颜色及其出现次数之和。题目提供了输入输出样例,并解释了树链剖分、重儿子和轻儿子的概念,以及启发式合并的三个关键步骤。解决方案包括理解并实现启发式合并算法来处理树形结构的数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接: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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值