1. 程式人生 > 其它 >洛谷P4322.最佳團體

洛谷P4322.最佳團體

題目大意

一個 \(n(1\leq n\leq 2500)\) 個節點的森林,每個點 \(i\) 有權值 \(s_{i},p_{i}(0<s_{i},p_{i}\leq 10^4)\) 以及父親 \(r_{i}\) 。每個節點可以被選擇的前提是其父親已經被選擇,從中選出 \(k(1\leq k\leq n)\) 個節點,使得 \(\sum_{j=1}^{k}\frac{p_{j}}{s_{j}}\) 的值最大,求出這個值。

思路

這很顯然是一個分數規劃問題,我們可以讓每個節點 \(i\) 的權值為 \(p_{i}-s_{i}\times mid\) ,然後二分來判斷。我們用節點 \(0\) 作為根把所有森林連起來變成一棵樹,每次判斷時作樹形 \(dp\)

,設 \(dp[v,i]\) 為在以 \(v\) 為根的子樹中選取了 \(i\) 個點所獲得的最大權值,於是在合併子樹 \(to\) 的過程中,有:

\[dp[v,j+k]=max(dp[v,j+k],dp[v,j]+dp[to,k])_{0\leq j<size[v],0\leq k\leq size[to]} \]\[dp[v,i]=dp[v,i-1]+val[v](v\neq 0) \]

每次 \(dp\) 時將所有 \(dp[v,0]\) 初始為 \(0\) ,其餘為 \(-inf\) 。最後根據 \(dp[0,k]\) 是否 \(\geq0\) 來判斷即可,每次 \(check\) 可以 \(O(n^2)\)

完成,因為每兩個節點 \(x,y\) 僅會在 \(lca(x,y)\) 處對遍歷次數產生 \(1\) 的貢獻,複雜度 \(O(n^2logn)\)

程式碼

#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair<int, int> PII;
#define all(x) x.begin(),x.end()
//#define int LL
//#define lc p*2+1
//#define rc p*2+2
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 2510;

vector<int>G[maxn];
LL K, N, S[maxn], P[maxn], R[maxn];
double dp[maxn][maxn];
double val[maxn];
LL vsize[maxn];

void add_edge(int from, int to)
{
	G[from].push_back(to);
}

void dfs(int v)
{
	vsize[v] = 1;
	dp[v][0] = 0;
	if (!G[v].size())
	{
		dp[v][1] = val[v];
		return; 
	}
	for (int i = 0; i < G[v].size(); i++)
	{
		int to = G[v][i];
		dfs(to);
		for (int j = vsize[v] - 1; j >= 0; j--)
		{
			for (int k = vsize[to]; k >= 0; k--)
				dp[v][j + k] = max(dp[v][j + k], dp[v][j] + dp[to][k]);
		}
		vsize[v] += vsize[to];
	}

	if (v)
	{
		for (int i = vsize[v]; i > 0; i--)
			dp[v][i] = dp[v][i - 1] + val[v];
	}
}

bool check(double x)
{
	for (int i = 0; i <= N; i++)
	{
		for (int j = 1; j <= K; j++)
			dp[i][j] = -inf;
	}
	for (int i = 1; i <= N; i++)
		val[i] = P[i] - S[i] * x;
	dfs(0);
	if (dp[0][K] - 0 > -eps)
		return true;

	return false;
}

void solve()
{
	double lo = 0, hi = inf;
	for (int i = 1; i <= 100; i++)
	{
		double mid = (lo + hi) / 2;
		if (check(mid))
			lo = mid;
		else
			hi = mid;
	}
	cout << setiosflags(ios::fixed) << setprecision(3) << lo << endl;
}

int main()
{
	IOS;
	cin >> K >> N;
	for (int i = 1; i <= N; i++)
	{
		cin >> S[i] >> P[i] >> R[i];
		add_edge(R[i], i);
	}
	solve();

	return 0;
}