树上背包的O(nV)做法 HDU1561 洛谷P1064

这篇博客介绍了如何将树上背包问题的复杂度从O(nV^2)优化到O(nV),主要讨论了两种方法。第一种方法是从叶子节点递归到根节点计算最大价值,虽然复杂度较高,但思路直观。第二种O(nV)的方法改变了dp数组的含义,使其表示不包括自身节点的子树作为泛化物品的价值。通过 dfs 实现“push down”和"push up"操作,实现了效率的提升。博客提供了具体的代码实现,并指出该算法可以应用于洛谷P1064问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考:
这里介绍了2种O(nV)的做法
国家集训队论文

(虽然连续依赖可能构成森林, 但我们可以虚构一个超级根节点解决这个问题)

普通的做法:
HDU 1561
(我的朴素做法( n V 2 nV^2 nV2)用排序逃避了写 d f s dfs dfs…实际上是完全没有必要的…用 d f s dfs dfs反而更加好写, 并且稍微 改变就能改成更高效的算法.)
对于一棵树, 我们考虑从叶子节点一路推到根节点来计算总的最大价值. 按深度从深到浅依次枚举每个节点, 每个节点枚举子节点的每个泛化物品更新当前节点的泛化物品. 这样的做法很容易想到, 但是复杂度是 O ( n V 2 ) O(nV^2) O(nV2)的.
d p [ i ] [ j ] dp[i][j] dp[i][j]表示考虑了 i i i节点及其子树, 当容量为j时, 得到的最大价值------注意, 这里的答案严格意义上是无效的, 因为并没有考虑其祖先, 而树上背包要求我们必须得到祖先才能选择后代------当然, 当我们考虑完所有的节点(一直到根节点)这个答案自然就成正确的了.
实际上, “枚举儿子的泛化物品, 更新当前节点的泛化物品”, 这个过程被称作"求泛化物品的和", 复杂度的确是 O ( n V 2 ) O(nV^2) O(nV2)的, 核心代码大概长这个样子:

for (auto x:lp)//枚举父亲
        for (auto i:son[x])//枚举儿子
            for (int j = m + 1; j >= 0; --j)//枚举父亲的容量
                for (int k = 1; k <= m + 1 && j - k >= 0; ++k)//枚举儿子的容量
                    checkMax(dp[x][j], dp[x][k] + dp[i][j - k]);//泛化物品求和

导致这个复杂度的原因在于枚举了两层容量, 更确切地说, d p [ i ] [ j ] dp[i][j] dp[i][j]维护了i向下这棵完整子树的信息, 而向上更新的过程中没有利用到"x(i的父亲)想要取到 i i i的贡献必须先取i"这个特性, 以至于只能把 s s s i i i看成各自独立的两个个体暴力合并, 这一点限制了我们必须求泛化物品的和. 那有没有更好的办法呢?

O ( n V ) O(nV) O(nV)的做法:

d p [ i ] [ j ] dp[i][j] dp[i][j]有了新的含义: 考虑将第 i i i个节点不包含i本身的子树看作泛化物品, 当其容量为 j j j时价值为 d p [ i ] [ j ] dp[i][j] dp[i][j]. 这样做有什么好处呢? 对于一个节点 i i i,我们先强制取它的儿子 s s s计入贡献,并且假设我们已经通过某种方法知道了 F s Fs Fs, 那么 F ( i , j ) F(i,j) F(i,j)就由 F ( s , j − w [ s ] ) + v [ s ] F(s,j-w[s])+v[s] F(s,jw[s])+v[s]转移. 从这里就能看出把 d p [ i ] [ j ] dp[i][j] dp[i][j]定义为将自己排除在外的子树的好处: 能够直接用泛化物品(儿子)和普通物品(儿子本身)更新泛化物品(父亲). 这个过程被称作"求泛化物品的并", 复杂度是 O ( n V ) O(nV) O(nV)的, 核心代码是这样:

void dfs(int now, int V) {
    for (auto son:to[now]) {
        for (int i = 0; i <= V; ++i)
            dp[son][i] = dp[now][i];
        dfs(son, V - v[son]);
        for (int i = v[son]; i <= V; ++i)
            checkMax(dp[now][i], dp[son][i - v[son]] + w[son]);
    }
}

因为不可避免有两种操作: “push down”(把父亲当前的泛化物品传给儿子)和"push up"(用儿子计算的值和儿子本身求并更新父亲), 所以必须用 d f s dfs dfs解决.
实际上, d p [ i ] [ j ] dp[i][j] dp[i][j]维护的是考虑前若干个儿子的贡献, 当前 i i i节点的泛化物品—只有当儿子都枚举完后, 才是除自己外的子树泛化物品.

这里是 H D U 1561 HDU1561 HDU1561 O ( n V ) O(nV) O(nV)代码:

int n, m,dp[205][205],w[205];
vector<int> to[205];
void dd() {
    for (int i = 0; i <= n; ++i) {
        for (int j = 0; j <= m + 1; ++j) {
            printf("[%d,%d]:%d ", i, j, dp[i][j]);
        }
        enter;
    }
}
void dfs(int now, int V) {
    for (auto son:to[now]) {
        for (int i = 0; i <= V; ++i)
            dp[son][i] = dp[now][i];
        dfs(son, V - 1);
        for (int i = 1; i <= V; ++i)
            checkMax(dp[now][i], dp[son][i - 1] + w[son]);
    }
}
void init() {
    while (~scanf("%d", &n) && n) {
        Mem(dp, 0);
        m = read();
        for (int i = 0; i <= n; ++i)to[i].clear();
        for (int i = 1; i <= n; ++i) {
            int a = read(), b = read();
            to[a].emplace_back(i);
            w[i] = b;
        }
        dfs(0, m);
        write(dp[0][m]), enter;
    }
}

用树上背包的写法, 洛谷 P1064 金明的预算方案 就可以直接秒了. 其实依赖背包完全可以全写成树上背包, 只不过空间要开大一点罢了.

int V, n,w[70],v[70],dp[70][40000];
vector<int> to[70];

void dfs(int now, int W) {
    for (auto son:to[now]) {
        for (int i = 0; i <= W; ++i) {
            dp[son][i] = dp[now][i];
        }
        dfs(son, W - w[son]);
        for (int i = w[son]; i <= W; ++i) {
            checkMax(dp[now][i], dp[son][i - w[son]] + v[son]);
        }
    }
}

void init() {
    V = read(), n = read();
    for (int i = 1; i <= n; ++i) {
        w[i] = read(), v[i] = read();
        v[i]*=w[i];
        int fa = read();
        to[fa].emplace_back(i);
    }
    dfs(0, V);
    int ans=0;
    for (int i = 0; i <=V; ++i) {
        checkMax(ans,dp[0][V]);
    }write(ans),enter;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值