题目描述
题目描述
某个公司有\(n\)个人, 上下级关系构成了一个有根树。其中有个人是叛徒(这个人不知道是谁)。对于一个人, 如果他
下属(直接或者间接, 不包括他自己)中叛徒占的比例超过\(x\),那么这个人也会变成叛徒,并且他的所有下属都会变
成叛徒。你要求出一个最小的\(x\),使得最坏情况下,叛徒的个数不会超过\(k\)。
输入格式
第一行包含两个正整数\(n,k(1<=k<=n<=500000)\)。
接下来\(n-1\)行,第\(i\)行包含一个正整数\(p[i+1]\),表示\(i+1\)的父亲是\(p[i+1](1<=p[i+1]<=i)\)。
输出格式
输出一行一个实数\(x\),误差在\(10^-6\)以内都被认为是正确的。
自己想出来的\(QWQ\),刚开始想的是二分\(+dp\),但是最后搞出来的方程发现和题解差不多
题解
思路细想的话非常绕!!!
首先考虑这么两个事儿
- 最坏情况一定是子节点是第一个叛徒;
因为假如某节点\(i\)变成叛徒,他的父亲\(fa\)不会受影响,那么他的叶节点\(leaf\)变成叛徒,反而可能把以\(i\)为根的子树全变成叛徒,创造更多的叛徒,甚至把\(fa\)也变成叛徒 - 最坏情况一定是一棵子树全部是叛徒,而不会出现森林;
我们考虑树上\(dp\)
首先统计出子树\(i\)的大小
那么让这颗子树变成叛徒的条件是什么呢,宁先想着,我待会儿和宁说
一个子树变成叛徒的边界条件是啥呢,就是求使当前子树变成叛徒的最大\(x\),\(x\)再大一点点就不行了,我们把这个稍微大一点点的\(x\)叫做\(x_1\),而我们求的是不让子树大小大于\(k\)的\(x\)的最小值,宁想一想这俩东西的关系
可以发现我们要求的答案就是所有大小大于\(k\)的子树的\(x_1\)的最大值,又因为是稍微大一点点,所以我们想成一样大
然后就可以\(dp\)了
此时回到上面的问题,让一颗子树变成叛徒的条件就是有一棵他的儿子叛变,而且以儿子为根的子树的大小在可以整棵子树所占的比例大于等于当前节点叛变要求的\(x\)
这俩条件需要同时满足,所以我们求其中的较小值,即以下
minn = min( dp[son], 1.0 * siz[v] / ( siz[now] - 1 ) ) // 注意乘1.0,如果( double )转高精的话取完min就会变成0,我WA了一上午
然后我们在它的每棵子树的\(minn\)中取一个最大值就是当前节点恰好叛变的\(x\),也是恰好不叛变的值
最后在每个大小大于\(k\)的子树的\(dp\)里取个最大值,就是恰好不让所有的大于\(k\)的子树叛变的\(x\)即答案
#include<bits/stdc++.h>
using namespace std;
#define rint register int
int n, k;
int b[500010];
double dp[500010], ans;
vector< int > vec[500010];
inline int read( void ){
int re = 0, f = 1; char ch = getchar();
while( ch > '9' || ch < '0' ){
if( ch == '-' ) f = -1;
ch = getchar();
}
while( ch >= '0' && ch <= '9' ){
re = re * 10 + ch - '0';
ch = getchar();
}
return re * f;
}
inline void dfs( int now ){
b[now] = 1;
for( rint i = 0; i < vec[now].size(); i++ ){
int v = vec[now][i];
dfs( v );
b[now] += b[v];
}
dp[now] = ( b[now] == 1 );
if( b[now] != 1 ){
for( rint i = 0; i < vec[now].size(); i++ ){
int v = vec[now][i];
dp[now] = fmax( dp[now], fmin( dp[v], 1.0 * b[v] / ( b[now] - 1 ) ) );
}
}
if( b[now] > k ){
ans = max( ans, dp[now] );
}
}
int main( void ){
n = read(); k = read();
for( rint i = 2; i <= n; i++ ){
int u; u = read();
vec[u].push_back( i );
}
dfs( 1 );
cout << ans;
return 0;
}