[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%。。。