链接: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
思路:
看到这个题目,首先考虑到的是每次取的应该是靠下面的点,相当于从这个树上的叶子节点开始dfs。那么问题就在于如何选择,很容易想到每次选择的是覆盖范围最远的点,但是这样也很容易找到反例。
这种情况下,选择节点5,6就明显好过选择6,3,2,1,然后看了题解发现了一个很妙的思路,那就是把k[i]的节点转为为选子节点或它本身之间的最小值,也就是k[u]=max(k[u],k[v]-1);这样在这个图中,k[4]=9,k[3]=8……以此类推,而新的k值可以通过dfs的方式更新。
这时候就可以考虑点的选取,首先叶子节点是肯定要选取的,一个点如果无法在它的子节点被选取能覆盖的最大值覆盖到,那么它也需要被选取。即遍历完所有子节点被选情况的时候,该节点的值依旧为0,可以发现需要一个新的数组来保存一个点被选取覆盖到时,它的k值大小,f[u]=max(f[u],f[v]-1);如果遍历完所有子节点仍为0,那么这个点就要被选取,同时它能覆盖到的范围就是k[u]。
一开始思维错在我没有考虑用一个数组记录被覆盖到时的最大范围,而是认为当父节点的k值大于子节点是就应该选择父节点,当然这种思路也很容易找到反例。
代码:
#include <bits/stdc++.h>
#include <algorithm>
#include<iostream>
#include <stdio.h>
#define INF 0x3f3f3f3f
const int maxn=500005;
using namespace std;
typedef long long ll;
//int pre[maxn];
//set<int>leave;
vector<int>tree[maxn];
int k[maxn];
int f[maxn];
int cnt=0;
void dfs(int u,int fa){
for(int i=0;i<tree[u].size();i++){
int v=tree[u][i];
if(v==fa)
continue;
dfs(v,u);
f[u]=max(f[u],f[v]-1);
k[u]=max(k[u],k[v]-1);
}
if(f[u]==0){
f[u]=k[u];
cnt++;
}
}
int main(){
int n,u,v;
cin>>n;
for(int i=2;i<=n;i++){
cin>>u;
tree[i].push_back(u);
tree[u].push_back(i);
}
for(int i=1;i<=n;i++)
cin>>k[i];
dfs(1,0);
cout<<cnt<<endl;
return 0;
}