题目描述
一棵n个点的有根树,1号点为根,相邻的两个节点之间的距离为1。树上每个节点i对应一个值k[i]。每个点都有一个颜色,初始的时候所有点都是白色的。
你需要通过一系列操作使得最终每个点变成黑色。每次操作需要选择一个节点i,i必须是白色的,然后i到根的链上(包括节点i与根)所有与节点i距离小于k[i]的点都会变黑,已经是黑的点保持为黑。问最少使用几次操作能把整棵树变黑。
题解
首先分析题意,发现题目中 '每次操作需要选择一个节点i,i必须是白色的' 这一限制其实是没有意义的,因为我们只要从上向下染色,是不会碰到染色的点的,因为每个点只能向上传递颜色。
那么问题就转换成最少选几个点,使整颗树变成黑色。
由于根节点是一定要染的,那么我们首先考虑贪心:从根节点向上染色,并记录能染到的最远距离,然后累加。
但其实可以很容易想到一种数据:2 -> 99 -> 1 -> 1 -> 1 (代表ki) 显然用上述的贪心思想不能得到最优解。
那怎么办呢? 怎么解决向上传递的路径中有可能对答案有贡献的点?
显然,不满足贪心的解法的情况为:选一个点的子节点比选当前点,往上走的距离要远。所以我们要更新每一个节点的K属性,使得当前子树最优(其实就相当于一种等价代换)。
那么我们得到这个新的K之后,继续贪心就可以拉~
代码
#pragma GCC optimize(2) #include<bits/stdc++.h> #define ls rt<<1 #define rs rt<<1|1 #define pb push_back #define fi first #define se second #define ios ios::sync_with_stdio(0); using namespace std; typedef long long LL; typedef pair<int, int> PII; typedef vector<int> VI; const int maxn = 1e5 + 6; const LL inf = 0x3f3f3f3f3f3f3f3f; const int mod = 998244353; LL qp(LL x,LL y){LL ans=1;x%=mod;while(y){if(y&1) ans=ans*x%mod;x=x*x%mod;y>>=1;}return ans;} LL inv(LL x){return qp(x,mod-2);} //head int n,ans,k[maxn],f[maxn]; VI G[maxn]; void dfs(int u,int fa){ int ff=0; for(int v:G[u]) if(v!=fa){ dfs(v,u); ff=max(ff,f[v]-1); } if(!ff) {ans++;f[u]=k[u];} else f[u]=ff; k[fa]=max(k[fa],k[u]-1); } int main(){ scanf("%d",&n); for(int i=1;i<n;i++){ int v;scanf("%d",&v); G[i+1].pb(v);G[v].pb(i+1); } for(int i=1;i<=n;i++) scanf("%d",&k[i]); dfs(1,0); printf("%d\n",ans); }