Bootstrap

黑白树 (树形 贪心)

黑白树

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

Solution: 根据题目描述,我们很容易想到从叶子节点 x 以 k[x] 的距离往上覆盖,当k[x] 为0时,染色数就++,但这时候选择哪个节点再进行染色是最优的呢?是当前恰好不能被 x 覆盖的当前节点,还是已经被 x 覆盖的结点中 k[i] (动态维护) 最大的一个和当前结点的 k 相比中较大的一个?所以当然是后者,但是你会有疑问,那已经被覆盖了的点不是不能再被染色了吗?注意这时候只是选择要染的点,而不是按照这个顺序染,当要染的点都挑选完毕,你可以从根往下染嘛。是吧~

#include<iostream>
#include<cstring>

using namespace std;
const int N = 1e5+7;
int h[N],e[N*2],ne[N*2],idx;
int k[N],ans;

void add(int a,int b){
	e[idx] = b,ne[idx] = h[a],h[a] = idx ++;
}

int DFS(int u,int fa)
{
	int num = 0;
	for(int i=h[u];i!=-1;i=ne[i]){
		int j = e[i];
		if(j==fa) continue;
		num = max(num,DFS(j,u));    //所有已经染过色的孩子能覆盖到的最大值
	}
	if(num==0){    //所有染过色的孩子都覆盖不到它
		ans++;return k[u]-1;
	}
	k[fa] = max(k[fa],k[u]-1);    //维护每个结点能往上覆盖的最大距离
	return num-1;
}

int main()
{
	int n,t;
	cin>>n;
	memset(h,-1,sizeof h);
	for(int i=2;i<=n;i++){
		cin>>t;
		add(t,i),add(i,t);
	}
	for(int i=1;i<=n;i++) cin>>k[i];
	DFS(1,0);
	cout<<ans<<endl;
	return 0;
}