《树上(分组)背包》基础概念

一、算法概述

  1. 问题定义:树上(分组)背包问题是将背包问题与树结构结合的优化问题。在树形结构中,每个节点对应一个物品,节点的子树构成物品分组。目标是在背包容量限制下,从树的节点(物品)中选择,使得所选物品的总价值最大(或满足其他优化目标)。
  2. 应用场景:适用于资源分配、任务调度等具有树形依赖关系的场景。例如,在公司组织架构树中分配资源,每个部门(节点)有不同资源需求和贡献价值,需在总资源限制下实现最大收益。

二、算法思路

  1. 状态定义
    • dp[i][j] 表示以节点 i 为根的子树中,选择容量总和为 j 的物品时能获得的最优价值。
    • sumVol[i] 记录以节点 i 为根的子树中所有子节点的总容量。
  2. 状态转移核心
    • 基于树的递归结构,从叶子节点向根节点自底向上计算。
    • 对于每个节点,先处理其子树的背包问题,再结合自身物品属性进行状态转移。
    • 采用类似分组背包的策略,将节点的子树视为不同分组,遍历每个分组内物品的不同选择情况。
  3. 辅助函数作用
    • KnapsackTree_InitValue 用于初始化状态初始值。
    • KnapsackTree_InfValue 定义非法状态值。
    • KnapsackTree_Opt 实现状态转移时的最优计算(如取最大值)。
    • KnapsackTree_Post 将递归过程中的临时数据转移到持久化的 dp 数组中。

三、伪代码实现

1. 数据结构与全局变量定义

常量 maxn = 1010  // 最大节点数
常量 maxv = 110  // 最大背包容量
结构体 Item:
    vol: 整数  // 物品体积
    wei: 整数  // 物品价值
数组 items[1..maxn]  // 存储所有物品信息
数组 child[0..maxn][]  // 邻接表存储树结构,child[i] 存储节点 i 的子节点
二维数组 dp[0..maxn][0..maxv]  // dp[i][j] 表示以 i 为根,容量 j 时的最优价值
数组 sumVol[0..maxn]  // 记录以 i 为根的子树总容量
整数 n  // 物品(节点)数量
整数 V  // 背包最大容量

2. 核心函数实现

算法:树上分组背包
输入:树的节点数量 n,背包容量 V,树结构和物品信息
输出:背包容量为 V 时的最大价值

function KnapsackTree_InitValue(u):
    // 初始化状态初始值
    return 0

function KnapsackTree_InfValue():
    // 返回非法状态值
    return -1000000000

function KnapsackTree_Opt(curVal, preVal, itemWei):
    // 状态转移的最优计算,取最大值
    return max(curVal, preVal + itemWei)

function KnapsackTree_Post(u, dpu[]):
    // 将临时数据 dpu 转移到 dp 数组
    for j from 0 to V:
        // 当前物品不选
        if j == 0:
            dp[u][j] = KnapsackTree_InitValue(u)
        else:
            dp[u][j] = KnapsackTree_InfValue()
        // 当前物品选择的情况
        if j >= items[u].vol:
            dp[u][j] = KnapsackTree_Opt(dp[u][j], dpu[j - items[u].vol], items[u].wei)

function KnapsackTree_Init(dpu[][], u):
    // 树上分组初始化
    pre = 0
    dpu[pre][0] = KnapsackTree_InitValue(u)
    for i from 1 to V:
        dpu[pre][i] = KnapsackTree_InfValue()
    return pre

function KnapsackTree_GetAnswer(root):
    // 获取根节点的最优答案
    ans = KnapsackTree_InfValue()
    for i from 0 to V:
        ans = KnapsackTree_Opt(ans, dp[root][i], 0)
    return ans

function KnapsackTree_DFS(u, fat):
    // 深度优先搜索处理树节点
    二维数组 dpu[2][maxv]
    pre = KnapsackTree_Init(dpu, u)
    cur = 1 - pre
    sumVol[u] = items[u].vol
    for v in child[u]:
        // 跳过父节点
        if v == fat:
            continue
        KnapsackTree_DFS(v, u)
        sumVol[u] += sumVol[v]
        for j from 0 to V:
            dpu[cur][j] = KnapsackTree_InfValue()
            for k from 0 to min(j, sumVol[v]):
                dpu[cur][j] = KnapsackTree_Opt(dpu[cur][j], dpu[pre][j - k], dp[v][k])
        swap(pre, cur)
    KnapsackTree_Post(u, dpu[pre])

// 主程序
输入 n, V
for i from 1 to n:
    输入 items[i].vol, items[i].wei, x
    将 i 添加到 child[x] 中
KnapsackTree_DFS(0, -1)
输出 KnapsackTree_GetAnswer(0)

四、算法解释

1. 初始化阶段

  • KnapsackTree_InitValue 函数将合法初始状态值设为0。
  • KnapsackTree_InfValue 定义非法状态值,用于表示不可行的选择情况。
  • KnapsackTree_Init 函数对每个节点的临时数组 dpu 进行初始化,容量为0时设为初始值,其他容量设为非法状态值。

2. 递归处理阶段(KnapsackTree_DFS)

  • 参数说明u 为当前节点,fat 为父节点,避免搜索时回退到父节点形成环。
  • 子树处理:对当前节点 u 的每个子节点 v,递归调用 KnapsackTree_DFS 处理子树,并更新 sumVol[u] 为子树总容量与自身物品容量之和。
  • 状态转移
    • 对于每个容量 j,先将 dpu[cur][j] 设为非法状态值。
    • 遍历子节点 v 的不同容量选择 k,通过 KnapsackTree_Opt 函数更新 dpu[cur][j],实现子树间的状态合并。
    • 交换 precur,交替使用临时数组记录状态。

3. 数据持久化阶段(KnapsackTree_Post)

  • 递归完成后,将临时数组 dpu 中的数据转移到持久化的 dp 数组中。
  • 分别考虑当前节点物品选与不选的情况,更新 dp[u][j]。不选时根据容量处理,选时结合临时数组数据和自身物品价值进行最优计算。

4. 结果获取阶段(KnapsackTree_GetAnswer)

  • 遍历根节点在不同容量下的 dp 值,通过 KnapsackTree_Opt 函数找到最大值,即为背包容量为 V 时的最优价值。

五、复杂度分析

  1. 时间复杂度
    • 树的遍历为 O ( n ) O(n) O(n),其中 n 为节点数。
    • 对于每个节点,状态转移时存在两层循环(遍历容量 j 和子树容量 k),假设树的深度为 h,每个节点平均子节点数为 c,最坏情况下,时间复杂度为 O ( n × V × V ) O(n \times V \times V) O(n×V×V)。实际中,若树结构平衡,复杂度接近 O ( n × V × c ˉ ) O(n \times V \times \bar{c}) O(n×V×cˉ),其中 c ˉ \bar{c} cˉ 为平均子节点数。
  2. 空间复杂度
    • 存储树结构的邻接表 child 空间复杂度为 O ( n ) O(n) O(n)
    • dp 数组空间复杂度为 O ( n × V ) O(n \times V) O(n×V)
    • 临时数组 dpu 空间复杂度为 O ( 2 × V ) O(2 \times V) O(2×V),可忽略不计。因此,总体空间复杂度为 O ( n × V ) O(n \times V) O(n×V)

  本文为作者(英雄哪里出来)在抖音的独家课程《英雄C++入门到精通》、《英雄C语言入门到精通》、《英雄Python入门到精通》三个课程的配套文字讲解,如需了解算法视频课程,请移步 作者本人 的抖音直播间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

英雄哪里出来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值