bzoj3090

树形dp

有一个比较明显的dp状态是dp[i][j]表示当前i节点的子树已经满足且i剩下j元钱的最小操作次数,这样复杂度比较高状态数已经有O(n*x)的了,转移再来x,肯定不行。

我们考虑把状态和dp值交换一下,因为操作次数最多只有n-1次,这样可以大大降低dp状态数,于是我们设dp[i][j]表示i的子树已经满足了,且操作了j次,根节点最多能有多少多余的钱,这里可以是负数,我们自然希望子树内满足之后,根节点钱尽量多,这样可以支持其他节点,转移就是背包dp,tmp[i+j+1]=min(tmp[i+j+1],dp[u][i]+dp[v][j]),表示我们把v的钱转移到u上,那么这样又进行了一次操作,如果dp[u][j]>=0,说明u节点满足了,那么我们可以不用子树来支持,那么tmp[i+j]=min(tmp[i+j],dp[u][j]),这样是说不转移,然后那么v上的钱就不可能转移上来了,最后答案就是dp[1][j]>=0的j。

如果状态过多,有时我们可以考虑交换状态和dp值,这样可以降低复杂度

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 2010;
int n, X;
vector<int> G[N];
int dp[N][N], tmp[N], v[N], size[N];
void dfs(int u, int last)
{   
    size[u] = 1;
//  for(int i = 0; i <= n; ++i) dp[u][i] = X - v[u];
    dp[u][0] = X - v[u];
    for(int i = 0; i < G[u].size(); ++i)
    {
        int v = G[u][i];
        if(v == last) continue;
        dfs(v, u);
        memset(tmp, -0x3f3f, sizeof(tmp));
        for(int j = 0; j <= size[u]; ++j)
            for(int k = 0; k <= size[v]; ++k)
            {
                tmp[j + k + 1] = max(tmp[j + k + 1], dp[u][j] + dp[v][k]);
                if(dp[v][k] >= 0) tmp[j + k] = max(tmp[j + k], dp[u][j]);
            }
        size[u] += size[v];
        for(int j = 0; j <= size[u] + 1; ++j) dp[u][j] = tmp[j];
    }
}
int main()
{
    scanf("%d%d", &n, &X);
    for(int i = 1; i <= n; ++i) scanf("%d", &v[i]);
    for(int i = 1; i < n; ++i)
    {
        int u, v;
        scanf("%d%d", &u, &v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    memset(dp, -0x3f3f, sizeof(dp));
    dfs(1, 0);
    for(int i = 0; i < n; ++i) if(dp[1][i] >= 0)
    {
        printf("%d\n", i);
        return 0;
    }
    return 0;
}
View Code

 

posted @ 2017-10-18 20:16  19992147  阅读(155)  评论(0编辑  收藏  举报