黑白树(DFS、贪心)

题目传送门

题目描述

一棵n个点的有根树,1号点为根,相邻的两个节点之间的距离为1。树上每个节点i对应一个值k[i]。每个点都有一个颜色,初始的时候所有点都是白色的。
你需要通过一系列操作使得最终每个点变成黑色。每次操作需要选择一个节点i,i必须是白色的,然后i到根的链上(包括节点i与根)所有与节点i距离小于k[i]的点都会变黑,已经是黑的点保持为黑。问最少使用几次操作能把整棵树变黑。

输入描述:

第一行一个整数n (1 ≤ n ≤ 10^5)
接下来n-1行,每行一个整数,依次为2号点到n号点父亲的编号。
最后一行n个整数为k[i] (1 ≤ k[i] ≤ 10^5)

样例解释:
对节点3操作,导致节点2与节点3变黑
对节点4操作,导致节点4变黑
对节点1操作,导致节点1变黑

输出描述:

一个数表示最少操作次数

示例1

输入

4
1
2
1
1 2 2 1

输出

3

分析:我们先读懂题目意思,若对某一白节点i进行染色,那么从节点i到根的链上所有与节点i距离小于k[i]的点都会变黑(说明染色方向单独朝向根),那么无论如何都必须对树的所有叶子节点染色,然后再根据每个节点权值k考虑是否对剩下的父节点染色,直到整棵树的节点全部变为黑节点,并且操作次数最少。

       但我们并不需急于先给叶子节点染色,我们可以先从根节点开始深搜每个节点的子节点,记录每个节点的深度。虽然我们知道每个节点的k值,但由于子节点和父节点处于不同深度,直接比较二者的k值的话不能确定哪个点是最优点。试想,我们可以把根节点看作基准,那么 该点的深度-该点的k值+1 就表示这个节点向上染色最多能够覆盖到的f深度,f深度越小说明该节点覆盖范围越大(相对)。这样的话我们运用贪心策略通过f深度来比较父节点和所有子节点哪个覆盖范围更大了。若子节点覆盖范围大,则同时更新父节点的f深度(父节点也被子节点涂色了,要继承子节点的光荣传统);若子节点能覆盖父节点,但f深度比父节点小,则父节点的f深度不变;若子节点不能不能覆盖父节点,则父节点要额外进行一次涂色。最终返回到根节点,整棵树被涂色完毕。

以下是三种情况:

                                                                           

首先,4,5,6分别为叶子节点,没有子节点为它染色,故需要自己染色。再看他们的覆盖范围,4,5,6节点的深度为4,k值分别为3,2,1,f深度分别为2,3,4,可见在这一层中,4节点的覆盖范围最大,能覆盖到深度2的地方。在上一层节点3 k值为1,f深度为3,没有子节点4覆盖范围大,根据贪心策略被子节点染色并继承子节点的f深度。

                                                                           

4,5,6节点的深度为4,k值分别为2,2,1,f深度分别为3,3,4,可见在这一层中,4,5节点的覆盖范围最大,都能覆盖到深度3的地方。在上一层节点3 k值为1,f深度为3,也能被子节点覆盖,根据贪心策略被子节点染色并继承子节点的f深度

                                                                           

4,5,6节点的深度为4,k值分别为1,1,1,f深度分别为3,3,3,可见在这一层中,4,5,6节点的覆盖范围一样,只能覆盖到深度3的地方。在上一层节点3 k值为1,f深度为2,子节点不能覆盖父节点,根据贪心策略这时父节点要进行染色

#include<cstdio>
#include<vector>
#include<iostream>
using namespace std;
const int M = 1e5+5;
const int inf = 0x3f3f3f;
int n,f[M],p[M],k[M],deep[M],t,ans;
vector<int>e[M];
void dfs(int x){
    f[x] = deep[x]-k[x]+1;   //f[x]表示每个节点向上染色最多能够覆盖到的深度,
                             //深度越小说明该节点的染色范围越大
    p[x] = inf;
    for(int i=0;i<e[x].size();i++){ //遍历一个节点的下一深度的所有节点
        deep[e[x][i]] = deep[x]+1;   //统计每个节点的深度
        dfs(e[x][i]);    //深搜子节点的
        f[x] = min(f[x],f[e[x][i]]); //记录每个节点的最优染色范围
        p[x] = min(p[x],p[e[x][i]]); //记录每个节点的实际染色范围,用以判断是否需要染色
    }
    if(p[x]>deep[x]){   //对于叶子节点或子节点的p深度无法覆盖该节点的,该节点涂色
        ans++;
        p[x] = f[x];
    }
}
int main(){
    scanf("%d",&n);
    for(int i=2;i<=n;i++){
        scanf("%d",&t);
        e[t].push_back(i);  //统计每个节点的子节点
    }
    for(int i=1;i<=n;i++){
        scanf("%d",&k[i]);  //统计每个节点的权值
    }
    deep[1] = 1;
    dfs(1);
    printf("%d\n",ans);

}
/*
4
1
2
1
1 2 2 1
*/

 

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值