[POI2017]Sabota【观察+树形Dp】

Online Judge:Bzoj4726

Label:观察,树形Dp,水题

题目描述

某个公司有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}\)以内都被认为是正确的。

样例

Input

9 3
1
1
2
2
2
3
7
3

Output

0.6666666667

Hint

当x取2/3时,最坏情况下3,7,8,9都是叛徒,超过了k=3。

答案中的x实际上是一个无限趋近于2/3但是大于2/3的数。

题解

看样例知道实际上是求一个转变的临界值。

观察一下发现,要叛变就是整个子树一起叛变,而且因为只选了一个叶子节点做叛徒,所以只会有这唯一一棵树叛变——不会出现多棵不相关的子树同时叛变。

那么我们只需要算出以每个点为根的子树叛变时所需的最大叛变比例\(lim[i]\)即可。上面这个定义有点繁琐,本质是比例\(lim\)调大,子树叛变越难。我们就是求一个最大值,使得在这个比例下,子树刚好叛变,如果提高一点就叛变不了了。

\(lim\)的方法类似树形dp。

最后统计时的代码如下,其中\(siz\)表示子树大小。求其中的最大值,因为比例达到这个最大值时,这些\(siz>k\)的子树都叛变不了(如果叛变了就不合法了)。

double ans=0;
for(int i=1;i<=n;i++)if(siz[i]>k)ans=max(ans,lim[i]);
printf("%.7f",ans);

完整代码如下:

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+10;
vector<int>e[N];
int n,k,siz[N];
double lim[N];//i的子树中叛变,能让i也叛变的最大k值 

void dfs(int x){
    siz[x]=1;
    for(int i=0;i<e[x].size();i++)dfs(e[x][i]),siz[x]+=siz[e[x][i]];
    if(siz[x]==1)lim[x]=1.0;
    for(int i=0;i<e[x].size();i++){
        int y=e[x][i];
        lim[x]=max(lim[x],min(lim[y],1.0*siz[y]/(siz[x]-1.0)));
    }
}
int main(){
    scanf("%d%d",&n,&k);
    for(int i=2,u;i<=n;i++){
        scanf("%d",&u);
        e[u].push_back(i);  
    }
    dfs(1);
    double ans=0;
    for(int i=1;i<=n;i++)if(siz[i]>k)ans=max(ans,lim[i]);
    printf("%.7f",ans);
}

猜你喜欢

转载自www.cnblogs.com/Tieechal/p/11522639.html
今日推荐