bzoj4753 树形dp+01分数规划
这是一个典型的树形背包+01分数规划。看见分数形式最大就应该想到01分数规划。
于是套用分数规划,每次用树形背包检验。
首先这是一棵树,不是一个森林,所以我们不用添加虚点。然后可以列出dp方程,具体代码。
然后每个点如果自己选了,那么父亲也要选,所以更新的时候,除了jyy也就是0号节点,都是从dp[u][1]开始更新,而且初值就是dp[u][0]=0,dp[u][1]=val,因为儿子选了,自己肯定会选,所以不能出现
dp[u][i]=dp[u][0]+dp[v][i]这种情况。
每次背包要从大到小枚举,常见技巧。
然后判断即是dp[0][k]>=0。
但是又有一个问题:val[0]是什么?如果是0的话,那么不就无法判断了?
那么我们这么设置一下,当u=0时,j可以枚举到0,因为jyy不算在k里,所以jyy就可以出现dp[0][i]=dp[0][0]+dp[v][i]的情况。但是val[0]还是设置成-inf,这样才可以取到最大值,否则一上来dp[0][1]就是0了。
#include<bits/stdc++.h> using namespace std; const int N = 2510; const double inf = 1e9, eps = 1e-5; int n, k; int r[N], size[N]; double ans; double dp[N][N], s[N], p[N], val[N]; vector<int> G[N]; void dfs(int u) { dp[u][0] = 0; dp[u][1] = val[u]; ++size[u]; for(int i = 0; i < G[u].size(); ++i) { int v = G[u][i]; dfs(v); for(int j = min(size[u], k); j >= (u == 0 ? 0 : 1); --j) for(int l = min(size[v], k); l >= 0; --l) if(j + l <= k) { // printf("dp[%d][%d]=%.10f dp[%d][%d]=%.10f dp[%d][%d]=%.10f ", u, l + j, dp[u][l + j], u, j, dp[u][j], v, l, dp[v][l]); dp[u][l + j] = max(dp[u][l + j], dp[u][j] + dp[v][l]); } size[u] += size[v]; } } bool C(double x) { for(int i = 0; i <= n; ++i) { size[i] = 0; val[i] = p[i] - s[i] * x; for(int j = 0; j <= k; ++j) dp[i][j] = -inf; } dfs(0); return dp[0][k] >= 0; } int main() { scanf("%d%d", &k, &n); s[0] = inf; for(int i = 1; i <= n; ++i) { scanf("%lf%lf%d", &s[i], &p[i], &r[i]); G[r[i]].push_back(i); } double l = 0, r = 1e4 + 1, mid; while(r - l > eps) { mid = (l + r) / 2.0; if(C(mid)) l = ans = mid; else r = mid; } printf("%.3f ", ans); return 0; }