编程|500分] 黑白树 时间限制:1秒
空间限制:32768K
题目描述
一棵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变黑
输出描述:
一个数表示最少操作次数
输入例子:
4 1 2 1 1 2 2 1
输出例子:
3
思路:
我们知道,最初的树都是白色的,而且染色是向上染色的,那么我们肯定就要将所有的叶子节点都进行染色,那么这里构成一个DAG图,我们可以跑拓扑排序模拟出每一次都选择度为0的没被染色的节点。
但是显然这样做是不对的。
假设有这样一种情况:
其K:4 10 2 2 1 1 1 1
这样的话,我们肯定先选8作为起点,染色到5.那么暴力去想的话继续以4为起点,染色到4,再以3为起点,染色到3…………..依次类推的话,其结果就是5.
但是显然我们的正解答案是2.
那么我们怎么办呢?
我们维护一个数组Go【i】,表示到点i,以上一个起点开始染色染色到这里还剩余能够染色的长度。
那么当Go【i】==0的时候,我们需要一个点要么是节点i,要么是以上一个起点到i这条链子上的某个点作为起点,继续向下跑。
那么我们需要另外一个数组dp【i】.表示跑到点i,以某一个点作为起点,这条链子上跑到节点i还剩余能够染色的最长的长度。
那么很显然,我们当Go【i】==0的时候,需要output++.同时比较a【i】和dp【i】的大小,如果是a【i】大,那么我们以i这个点作为起点继续向下跑,否则我们以dp【i】作为剩余长度继续跑,显然这种选项的起点在以上一个根到当前跟路径上的某一个点作为起点。
(我给出的样例就是跑到了4的时候,Go【i】==0.但是此时Dp【i】=7.a【i】=1.我们显然以7作为剩余长度继续跑下去,那么这个长度7的起点,无意就是编号为7的节点跑过来的,维护出来的最长值)
那么实现过程不难写出:
注意数组大小和初始化就没有别的什么了。
Ac代码:
#include<stdio.h>
#include<string.h>
#include<vector>
#include<queue>
using namespace std;
int degree[100040];
int a[100040];
int Go[100040];
int dp[100040];
vector<int >mp[100040];
int n;
void Top_Dp()
{
queue<int >s;
int output=0;
memset(Go,0,sizeof(Go));
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++)
{
if(degree[i]==0)
{
s.push(i);
}
}
while(!s.empty())
{
int u=s.front();
s.pop();
dp[u]=max(dp[u],a[u]);
if(Go[u]==0)
{
Go[u]=dp[u];output++;
}
for(int z=0;z<mp[u].size();z++)
{
int v=mp[u][z];
dp[v]=max(dp[v],dp[u]-1);
Go[v]=max(Go[v],Go[u]-1);
degree[v]--;
if(degree[v]==0)
{
s.push(v);
}
}
}
printf("%d\n",output);
}
int main()
{
while(~scanf("%d",&n))
{
memset(degree,0,sizeof(degree));
for(int i=2;i<=n;i++)
{
int fa;
scanf("%d",&fa);
mp[i].push_back(fa);
degree[fa]++;
}
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
Top_Dp();
}
}