[BZOJ4726][POI2017]Sabota?

[BZOJ4726][POI2017]Sabota?

试题描述

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

输入示例

9 3
1
1
2
2
2
3
7
3

输出示例

0.6666666667

数据规模及约定

见“输入

题解

正解是一个非常神的树形 dp,贴传送门,讲的挺好的。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;

int read() {
	int x = 0, f = 1; char c = getchar();
	while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
	while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
	return x * f;
}

#define maxn 500010
#define maxm 1000010

int n, K, m, head[maxn], nxt[maxm], to[maxm], fa[maxn], siz[maxn];
double f[maxn];

void AddEdge(int a, int b) {
	to[++m] = b; nxt[m] = head[a]; head[a] = m;
	swap(a, b);
	to[++m] = b; nxt[m] = head[a]; head[a] = m;
	return ;
}
void build(int u) {
	siz[u] = 1;
	for(int e = head[u]; e; e = nxt[e]) if(to[e] != fa[u])
		build(to[e]), siz[u] += siz[to[e]];
	return ;
}
void dp(int u) {
	f[u] = 0;
	if(siz[u] == 1) f[u] = 1;
	for(int e = head[u]; e; e = nxt[e]) if(to[e] != fa[u]) {
		dp(to[e]);
		f[u] = max(f[u], min(f[to[e]], (double)siz[to[e]] / (siz[u] - 1)));
	}
	return ;
}

int main() {
	n = read(); K = read();
	for(int i = 2; i <= n; i++) fa[i] = read(), AddEdge(i, fa[i]);
	
	build(1);
	dp(1);
	
	double ans = 0;
	for(int i = 1; i <= n; i++) if(siz[i] > K) ans = max(ans, f[i]);
	printf("%lf
", ans);
	
	return 0;
}

当然这题也可以二分 + 树形 dp 做:对于二分的答案 x,我们算出 f[i],表示节点 i 为根的子树中在 x 的比例条件下最坏有几个人叛变,转移显然。

不得不吐槽的是这样做得卡常数。当然这题非常妙,保证 n 到 1 是自下而上的顺序,所以树形 dp 或是树上预处理就可以不用递归了。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;

int read() {
	int x = 0, f = 1; char c = getchar();
	while(!isdigit(c)){ if(c == '-') f = -1; c = getchar(); }
	while(isdigit(c)){ x = x * 10 + c - '0'; c = getchar(); }
	return x * f;
}

#define maxn 500010

int n, K, m, fa[maxn], siz[maxn], f[maxn];
void build() {
	for(int i = 1; i <= n; i++) siz[i] = 1;
	for(int i = n; i > 1; i--) siz[fa[i]] += siz[i];
	return ;
}
bool check(double x) {
	for(int i = 1; i <= n; i++) f[i] = 1;
	for(int i = n; i > 1; i--) {
		f[fa[i]] = max(f[fa[i]], f[i]);
		if(f[i] > x * (siz[fa[i]] - 1)) f[fa[i]] = siz[fa[i]];
	}
	return f[1] <= K;
}

int main() {
	n = read(); K = read();
	for(int i = 2; i <= n; i++) fa[i] = read();
	
	build();
	double l = 0, r = 1;
	while(r - l > 1e-7) {
		double mid = (l + r) * 0.5;
		if(check(mid)) r = mid; else l = mid;
	}
	printf("%lf
", l);
	
	return 0;
}

非递归真的好快。。。运行时间大概是递归版的 15.79%。。。

原文地址:https://www.cnblogs.com/xiao-ju-ruo-xjr/p/6502288.html