BZOJ 4726 [POI2017]Sabota?:树形dp

传送门

题意

某个公司有 $ n $ 个人,上下级关系构成了一个有根树。其中有个人是叛徒(这个人不知道是谁)。对于一个人, 如果他下属(直接或者间接, 不包括他自己)中叛徒占的比例超过 $ x $ ,那么这个人也会变成叛徒,并且他的所有下属都会变成叛徒。你要求出一个最小的 $ x $ ,使得最坏情况下,叛徒的个数不会超过 $ k $ 。$ (1 \leq k \leq n \leq 500000) $

题解

首先有一个显然的二分做法,然而不知道为什么会被卡……

于是考虑能不能 $ O(n) $ 做。

首先有两个结论:

  • 最坏情况下,最初的那一个叛徒一定是叶子结点。
  • 如果一个非叶子节点叛变,则它其中的一个儿子一定叛变

所以最终所有的叛徒一定是原树中的一棵子树。

$ f[i] $ 表示要使节点 $ i $(及其下属)恰好不能叛变的最小比例 $ x $

因为所有 $ size[i] > k $ 的子树都不能叛变

所以 $ ans = max(f[i]) \quad (size[i] > k) $

然后考虑怎样求 $ f[i] $

$ f[i] $ 也等价于恰好让 $ i $ 叛变的最大比例 $ x $

对于每一个 $ i $ ,首先枚举它的哪个子树叛变了(假设是 $ j $ )

那么想让 $ i $ 叛变有两个条件:

  • 比例 $ x $ 满足能够让 $ j $ 叛变
  • 比例 $ x $ 满足 $ \dfrac{size[j]}{size[i] - 1} > x $

所以 $ f[i] = max(f[i], min(f[j], \dfrac{size[j]}{size[i]-1})) $

边界条件为:$ f[i] = \begin{cases} 1 \quad \text{叶子结点} \\ 0 \quad \text{非叶子节点} \end{cases} $

AC Code

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <vector>
#define MAX_N 500005

using namespace std;

int n,k;
int siz[MAX_N];
double ans=0;
double f[MAX_N];
vector<int> edge[MAX_N];

void read()
{
	scanf("%d%d",&n,&k);
	int x;
	for(int i=2;i<=n;i++)
	{
		scanf("%d",&x);
		edge[x].push_back(i);
	}
}

void dfs(int x)
{
	siz[x]=1,f[x]=(edge[x].size()==0);
	for(int i=0;i<edge[x].size();i++)
	{
		int t=edge[x][i];
		dfs(t),siz[x]+=siz[t];
	}
	for(int i=0;i<edge[x].size();i++)
	{
		int t=edge[x][i];
		f[x]=max(f[x],min(f[t],siz[t]/(siz[x]-1.0)));
	}
	if(siz[x]>k) ans=max(ans,f[x]);
}

void work()
{
	dfs(1);
	printf("%.9f\n",ans);
}

int main()
{
	read();
	work();
}
posted @ 2018-05-27 15:18  Leohh  阅读(166)  评论(0编辑  收藏  举报