首页 > 代码库 > bzoj4753

bzoj4753

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\n", 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\n", ans);
    return 0;
}
View Code

 

bzoj4753