首页 > 其他分享 >树上背包

树上背包

时间:2024-11-08 16:09:02浏览次数:1  
标签:sz 背包 int include 课程 为根 树上 dp

树上的背包问题,也就是背包问题与树形 DP 的结合。

例题:P2014 [CTSC1997] 选课

有 \(n\) 门课程,第 \(i\) 门课程的学分为 \(a_i\),每门课程有零门或一门先修课,有先修课的课程需要先学完其先修课,才能学习该课程。一位学生要学习 \(m\) 门课程,求其能获得的最多学分数。
数据范围:\(n,m \le 300\)

由于每门课最多只有一门先修课,与有根树中一个点最多只有一个父亲节点的特点类似。可以利用这个性质来建树,从而所有课程形成了一个森林结构。为了方便起见,可以新增一门 \(0\) 学分的课程(编号为 \(0\)),作为所有无先修课课程的先修课,这样原本的森林就变成了一棵以 \(0\) 号节点为根的树。

设 \(dp_{u,i,j}\) 表示以 \(u\) 为根的子树中,已经遍历了 \(u\) 号点的前 \(i\) 棵子树,选了 \(j\) 门课程的最大学分。

转移的过程结合了树形 DP 和背包问题的特点,枚举点 \(u\) 的每个子节点 \(v\),同时枚举以 \(v\) 为根的子树选了几门课程,将子树的结果合并到 \(u\) 上。

将点 \(x\) 的子节点个数记为 \(s_x\),以 \(x\) 为根的子树大小为 \(sz_x\),则有状态转移方程:\(dp_{u,i,j} = \max \{ dp_{u,i-1,j-k} + dp_{v,s_v,k} \}\),注意有一些状态是无效的,比如 \(k>j\) 或是 \(k>sz_v\) 时。

第二维可以通过滚动数组优化掉,此时需要倒序枚举 \(j\) 的值,同 0-1 背包问题。

该做法的时间复杂度为 \(O(nm)\),证明见 子树合并背包类型的dp的复杂度证明

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 305;
vector<int> tree[N];
int s[N], dp[N][N], n, m;
int dfs(int u) {
    int sz_u = 1;
    dp[u][1] = s[u];
    for (int v : tree[u]) {
        int sz_v = dfs(v);
        for (int i = min(sz_u, m + 1); i > 0; i--)
            for (int j = 1; j <= sz_v && i + j <= m + 1; j++)
                dp[u][i + j] = max(dp[u][i + j], dp[u][i] + dp[v][j]);
        sz_u += sz_v;
    }
    return sz_u;
}
int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        int k;
        scanf("%d%d", &k, &s[i]);
        tree[k].push_back(i);
    }
    dfs(0);
    printf("%d\n", dp[0][m + 1]);
    return 0;
}

习题:P3177 [HAOI2015] 树上染色

解题思路

显然设 \(dp_{u,i}\) 代表以 \(u\) 为根节点的子树,将其中 \(i\) 个点染成黑色的状态。

但是这个值存什么呢?如果直接表示子树内黑点、白点间的收益,这个状态没有办法转移,因为子树内最大化收益的染色方案被合并上去后未必是最优的方案,也就是有后效性。

考虑每条边对最终答案的贡献,如果一条边的两侧有一对黑点或白点,则这条边对这两个点构成的路径是有贡献的。也就是说,一条边对总答案的贡献次数等于边的两侧同色点个数的乘积。而子树内每条边对总答案的贡献这个状态值在子树合并过程中是可以向上传递的。

因此 \(dp_{u,i}\) 代表以 \(u\) 为根节点的子树中有 \(i\) 个点被染成黑色后子树内每一条边对总答案的贡献,类似树上背包,当要合并某个子节点 \(v\) 对应的子树时,枚举子树中的黑点数量 \(j\),则对应的转移为 \(dp_{u,i-j} + dp_{v,j} + w(u,v) \times c(u,v)\),其中 \(w(u,v)\) 代表边权,\(c(u,v)\) 代表 u-v 这条边对总答案的贡献,而 \(c(u,v) = j \times (k - j) + (sz_v - j) \times (n - sz_v - (k - j))\)。

参考代码
#include <cstdio>
#include <utility>
#include <vector>
#include <algorithm>
using std::pair;
using std::vector;
using std::min;
using std::max;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 2005;
int n, k;
vector<PII> tree[N];
LL dp[N][N]; // dp[u][i] u的子树内染i个黑点时边对答案的总贡献
int dfs(int u, int fa) {
    int sz_u = 1;
    for (PII p : tree[u]) {
        int v = p.first, w = p.second;
        if (v == fa) continue;
        int sz_v = dfs(v, u);
        for (int i = min(k, sz_u); i >= 0; i--) {
            for (int j = 1; j <= sz_v && i + j <= k; j++) {
                LL black = 1ll * w * j * (k - j);
                LL white = 1ll * w * (sz_v - j) * (n - sz_v - (k - j));
                dp[u][i + j] = max(dp[u][i + j], dp[u][i] + dp[v][j] + black + white);
            }
            // 若将j=0放在循环中,则会立马更新dp[u][i]
            // 导致接下来计算dp[u][i+j]时用到的dp[u][i]已经不是之前的值了
            LL white = 1ll * w * sz_v * (n - sz_v - k);
            dp[u][i] += dp[v][0] + white;
        }
        sz_u += sz_v;
    }
    return sz_u;
}
int main()
{
    scanf("%d%d", &n, &k);
    for (int i = 1; i < n; i++) {
        int u, v, w; scanf("%d%d%d", &u, &v, &w);
        tree[u].push_back({v, w}); tree[v].push_back({u, w});
    }
    dfs(1, 0);
    printf("%lld\n", dp[1][k]);
    return 0;
}

标签:sz,背包,int,include,课程,为根,树上,dp
From: https://www.cnblogs.com/ronchen/p/18535307

相关文章

  • 代码随想录算法训练营 day37 day38| 卡码网52.完全背包 518. 零钱兑换 II 377.
    学习资料:https://programmercarl.com/背包问题理论基础完全背包.html#算法公开课相比于01背包,完全背包中每个物品都可以无限次的放入组合:先遍历物品,再逆序遍历背包排列:先逆序遍历背包,再遍历物品学习记录卡码网52.携带研究资料(dp[i]代表当重量为i时的最大价值)点击查看代码n......
  • 树上差分
    近年的NOIP,对于树上差分的题目时有出现:(NOIP2015《运输计划》,NOIP2016《天天爱跑步》)。这些题目都要知道在树上从某个点到另一个点的所有路径。但是,暴力求解这种题目经常会TLE。这种题目需要使用树上差分。在讲树上差分之前,首先需要知道树的以下两个性质:任意两个节点之间......
  • P10161 [DTCPC 2024] 小方的疑惑 10 [构造 + 背包DP]
    P10161[DTCPC2024]小方的疑惑10Solution一开始看这题的时候,我们可能会觉得无从下手,这时不妨列出几种方案,计算它们的贡献,尝试得到一些启发。画来画去,发现无非就是并列和包含两种情况,并列就是()()()(),设它一共由\(x\)对括号组成,那么它的总贡献是\(x\times(x+1)\div......
  • 01背包与完全背包
    背包套路最大分成两步骤:1.状态表示 2.状态计算1.状态表示状态表示从两个角度来思考:(1)集合(2)属性(1)集合:满足某中条件下的所有选法的集合(2)属性:最大值/最小值/数量2.状态计算:所谓状态计算就是集合的划分,满足两种原则:(1)不重(2)不漏对与求最大值和最小值的问题可以重......
  • CF的背包DP (备用笔记)
    源自vjudge上找到题目,都是背包DP的变式------(推荐点点前两个字......
  • 背包九讲
    1.01背包问题一切的一切的基石!!!//v:c[i]toVf[i][v]=max(f[i-1][v],v[i-1][v-c[i]]+w[i]);解释:考虑第\(i\)位有选和不选两种选择,选了就减去c[i],附有w[i]的影响。优化:倒序可以省去第一维。为什么写成这样?很容易发现f[i][j]会被f[i][j-w[i]]所......
  • 背包九讲——树形背包问题(有依赖的背包)
    目录树形背包问题问题引入:问题解读:算法例题:10.有依赖的背包问题-AcWing题库题目:算法实现:代码实现:背包问题第七讲——树形背包问题(有依赖的背包)背包问题是一类经典的组合优化问题,通常涉及在限定容量的背包中选择物品,以最大化某种价值或利益。问题的一般描述是:有一......
  • 分组背包问题
    分组背包问题问题有NNN组物品和一个容量是VVV的背包。每组物品有......
  • 动态规划 01背包(算法)
    现有四个物品,小偷的背包容量为8,怎么可以偷得价值较多的物品如:物品编号:1   2   3   4 物品容量:2   3   4   5物品价值:3   4   5   8记f(k,w),当背包容量为w,可以偷k件物品,所能偷到的最大价值以f(4,8)为列,记录每......
  • 代码随想录 -- 动态规划 -- 01背包理论基础
    46.携带研究材料(第六期模拟笔试)思路:dp[i][j]含义:在(0,i)之间任意选取物品放入容量为j的背包中,使背包的价值最大。递推公式:当前背包容纳不下第i个物品,不选第i个物品,此时背包的价值:dp[i][j]=dp[i-1][j]。当前背包容纳得下第i个物品时,且选择第i个物品,此时背包的价值:dp[i][j......