最佳团体[JSOI2016]

https://www.luogu.com.cn/problem/P4322

题解

网上怎么这么多 \(O(n^3\log n)\) 假做法啊。。。一条链就卡掉了

假设0号节点是根 建出表示依赖关系的图 发现正好有 \(n\) 条边且每个点的父亲都比它编号小所以正好是一棵树

那么原问题就是要在树上取一个包含根的性价比最高的连通块

看到"性价比"考虑使用01分数规划 假设 \(x_i\in \{0,1\}\) 表示第 \(i\) 个人选不选 答案是 \(ans\) 那么

\(\dfrac{\sum x_iP_i}{\sum x_iS_i} \le ans\)

挪一下

\(\sum x_i(P_i-S_i*ans) \le 0\)

二分答案 \(mid\) ,如果上方左式可以大于0说明答案小了 否则答案大了

如何求出左式的最大值?

考虑dp,求出原树的dfs序,设 \(f[i][j]\) 表示考虑到dfs序为 \(i\) 的点,共选了 \(j\) 个点

\(V_i=P_i-S_i*mid\)\(a_i\) 是dfs序为 \(i\) 的点的 \(V\)

可以选择这个点并且进入它的子树,即

\(f[i+1][j] = \max(f[i+1][j], f[i][j-1]+a_i)\)

可以不选这个点,这样它子树里的所有点都不能选了,所以直接跳到下一个在它子树外的点,假设它的dfs序是 \(k\)

\(f[k][j]=\max(f[k][j],f[i][j])\)

这种按dfs序dp的方式常见于求包含根的连通块

先预处理一下每个点的dfs序和下一个它子树外的点的dfs序编号

一次dp是 \(O(n^2)\) 的,总时间 \(O(n^2\log n)\)

#include <bits/stdc++.h>
#define N 2505
using namespace std;

template <typename T>
inline void read(T &num) {
	T x = 0, ff = 1; char ch = getchar();
	for (; ch > '9' || ch < '0'; ch = getchar()) if (ch == '-') ff = -1;
	for (; ch <= '9' && ch >= '0'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ '0');
	num = x * ff; 
}

int m, n, a[N], b[N], dfn[N], ed[N], tme = -1;
int head[N], pre[N<<1], to[N<<1], sz;
double p[N], f[N][N], ans;

inline void addedge(int u, int v) {
	pre[++sz] = head[u]; head[u] = sz; to[sz] = v;
	pre[++sz] = head[v]; head[v] = sz; to[sz] = u;
}

void dfs(int x, int fa) {
	dfn[x] = ++tme;
	for (int i = head[x]; i; i = pre[i]) {
		int y = to[i]; if (y == fa) continue; dfs(y, x); 
	}
	ed[dfn[x]] = tme;
}

double check(double k) {
	for (int i = 0; i <= n+1; i++) {
		p[dfn[i]] = 1.0 * a[i] - k * b[i];
		for (int j = 0; j <= m; j++) f[i][j] = -1e9;
	} 
	f[0][0] = 0; 
	for (int i = 0; i <= n; i++) {
		for (int j = 0; j <= m; j++) {
			f[ed[i]+1][j] = max(f[ed[i]+1][j], f[i][j]);
			if (j > 0) f[i+1][j] = max(f[i+1][j], f[i][j-1] + p[i]);
		} 
	}  
	return f[n+1][m];
} 

int main() {
	read(m); read(n); ++m;
	for(int i = 1, x; i <= n; i++) {
		read(b[i]); read(a[i]); read(x);
		addedge(x, i);
	}
	dfs(0, 0);
	double l = 0, r = 1e4, mid = 0;
	while (r - l > 1e-5) {
		mid = (l + r) / 2;
		if (check(mid) > 0) {
			l = mid + 1e-7;
		} else r = mid - 1e-7;
	}
	printf("%.3lf\n", l);
	return 0;
}
posted @ 2021-01-04 22:17  AK_DREAM  阅读(62)  评论(0编辑  收藏  举报