链接:https://ac.nowcoder.com/acm/problem/13249
来源:牛客网
题目描述
一棵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
思路:我们发现其实不管采用怎样的染色策略,树的所有叶子节点是一定要操作的,因此我们很容易想到的一个贪心策略是自底向上进行模拟,对于当前操作的点,我们能够很容易知道这个点操作后能延伸到哪里,当我们向上回溯的时候直到叶子节点无法延伸到该点时我们对当前点进行操作,但是很容易证明这个贪心思想是错误的!
这道题其实是一种很简单的动态规划思想,只是为了增大难度放在了树上而已,如果我们简单的通过最远的操作点进行计数并不能在任意情况下达到最优解,很简单的反例就是某个非叶子节点的k值非常大,若当前点已经被其某个子节点覆盖到,我们是有可能通过操作这个点从而得到更优的解的,因此我们需要考虑动态规划,通过维护来自子节点往上最远的覆盖距离,当这个覆盖距离为0时,说明这个点无法覆盖,我们需要新增一个节点,我们选取的点应该是越过这个点可以往上走最远的节点涂黑。
#include<vector>
#include<stdio.h>
#include<algorithm>
using namespace std;
#define maxn 100005
int k[maxn],n,dp[maxn],ans;
vector<int> q[maxn];
void dfs(int u,int f){
int sz=q[u].size();
for(int i=0;i<sz;i++){
int v=q[u][i];
dfs(v,u);
k[u]=max(k[u], k[v]-1);
dp[u]=max(dp[u], dp[v]-1);
}
if(!dp[u]){
ans++;
dp[u]=k[u];
}
}
int main(void){
scanf("%d",&n);
for(int i=2,x;i<=n;i++){
scanf("%d",&x);
q[x].push_back(i);
}
for(int i=1;i<=n;i++)
scanf("%d",&k[i]);
dfs(1,0);
printf("%d\n",ans);
return 0;
}