题解 P5958 【[POI2017]Sabotaż】

· · 题解

本题解已经通过了,这次是勘误,去掉了反作弊,望管理员通过。

感谢 @void_basic_learner dalao 指出的错误。

树形DP入门题

题意描述

在一棵以1号节点为根节点的树上,有很多纯洁的白点,

BUT,突然有一个黑点出现(可能在任意位置)

它要染黑尽可能多的节点,而在一棵子树中,

只有当黑点的比例>x才可以染黑根节点(即整棵子树)

求x的最小值,使得整棵树中被染黑的节点数不超过k

如果你看不懂请走传送门

算法分析

一道很裸的树形DP,但思路很巧

显然本题有以下性质:

  1. 最坏情况下,最开始的叛徒是叶子结点
  2. 因为一个节点被染黑了,一起为根节点的子树将全黑,所以最终被染黑的一定是一颗子树

先设计状态:f(i)表示使得i不变黑的最小x

易得:f(i)也是使得i变黑的最大x

可知f(i)仅与f(son{i})以及soni的大小有关(这里的soni表示i的子节点)

那么我们用sum(i)表示i为根节点的子树的大小,sum(i)是需要提前用dfs预处理的

显然i被染黑仅必须满足以下两种情况:

  1. f(soni) > x,即i的某棵子树被染黑
  2. sum(soni)/(sum(i)-1) > x,即soni的被染黑足以导致i的被染黑

根据以上规律可以推出如下方程:

取$min$是因为需要同时满足条件1和条件2, 取$max$是因为需要答案最优 ~~(貌似貌似到这里就结束了呢)~~ 其实还有以下三点细节需要注意: 1. **对于叶子结点$a$,显然有$f(a)=1$** 2. **对于$ans$当$sum(a)<k$时是不需要考虑的,因为它合法,所以即使它被染黑也无所谓** 3. **针对上一条结论,易得$ans=max(ans,f(a))$,其中$sum(a)>=k$** 然后就去快乐$AC$吧...... ## 代码实现 ```cpp #include<cstdio> #include<algorithm> #include<cstring> #include<iostream> #include<vector> #define maxn 500050 using namespace std; int n,k,sum[maxn]; double f[maxn],ans; vector<int>v[maxn];//用vector存图笑哈哈 void dfs(int now,int fa){ sum[now]=1; for(int i=0;i<v[now].size();i++){ int to=v[now][i]; dfs(to,now); sum[now]+=sum[to]; }//dfs预处理sum[] if(sum[now]==1){f[now]=1.0;return;}//叶子结点的处理 for(int i=0;i<v[now].size();i++){ int to=v[now][i]; f[now]=max(f[now],min(f[to],(double)sum[to]/(sum[now]-1))); }//dp if(sum[now]>k) ans=max(ans,f[now]);//统计答案,注意if return; } int main(){ scanf("%d %d",&n,&k); int x; for(int i=2;i<=n;i++){ scanf("%d",&x); v[x].push_back(i); } dfs(1,0); printf("%.8lf",ans);//注意精度问题 return 0; } ``` ## 结语 安利[my blog](https://www.cnblogs.com/lpf-666/) (光速逃...