zoukankan      html  css  js  c++  java
  • 树形背包O(n * v^2)入门

    我虽然做了好几道树形背包的题,但是一直不是十分理解,对于每一道题,总是看题解就明白,然后换一道题自己写不出来。临近NOIP,gg让我们强化一下背包以及树形背包,我也恰有此打算,于是又开始从头学习了树形背包。

    看了好多博客以及论文之后,对树形背包确实有了一个全新的认识,尤其是这篇博客以及徐持恒的论文《浅谈积累背包问题》,对我有很大的帮助。两者都提到了泛化物品(当然这个名词最初是在背包九讲里面提到的)这个概念,我觉得这是对树形背包O(n * v2)做法的一种不同理解,不过我认为引入这个名词的主要目的还是对O(nv)的做法做出了解释。遗憾的是,我虽然用O(nv)的做法成功写出了一道题,然而却仍旧不是很懂。所以这篇博文主要是讲解O(n * v2)的做法,也算是整理自己的学习笔记吧。

    如果哪一天我把O(nv)的做法看懂了的话,可能还会来更这篇博客。

     

    上文已经提到,对于O(n * v2)的做法有两种不同的理解,那么我在这里就分别阐述一下。

    都以这道题为例

    一、用分组背包来理解

    首先题中给的依赖关系是一个森林,那么可以建立一个虚拟节点0,作为森林的根,形成一棵树。

    令dp[u][j]表示以 u 为根的子树中,选 j 门课(体积)能得到的最大学分。那么 u 一定要选(初始化dp[u][1] = val[u]),而对于子树内其他点的选取情况,可以把每一种选取方案看成一个物品,又因为每一种方案都是互斥的,每一组只能选一个,那么就是一个分组背包了。这里的组数,是 u 的儿子个数 p = |son(u)|,对于一个vi ∈son(u),他其实代表了j - 1个物品(因为还要选u),拿其中一个为例,dp[vi][k](0 <= k < j)这种选取方案才代表一个物品。

    现在考虑转移方程。按照分组背包的写法,我们应该先加一维,dp[u][k][j]表示以x为根的子树,选到第k组,选了 j 门课得到的最大学分。于是有dp[u][k][j] = max(dp[u][k - 1][j], dp[u][k - 1][j - h] + dp[v][sz][h])。注意,dp[v][sz][h]代表一个物品,sz是v的所有组数,因为要保证最优,所以一定从v的所有组数选完的状态转移到u。

    然后再模仿分组背包省去第二维,把 j 倒着枚举。

    核心代码:

     1 void dfs(int now)
     2 {
     3   for(int i = head[now]; i; i = e[i].nxt)
     4     {
     5       dfs(e[i].to);
     6       for(int j = m + 1; j; --j)
     7     for(int k = 0; k < j; ++k) //这一维正着倒着都行,有很多书上是倒着的
     8       dp[now][j] = max(dp[now][j], dp[now][j - k] + dp[e[i].to][k]);
     9     }
    10 }

    对于每一个节点只会进行一次O(v2)的分组背包,所以复杂度O(n * v2)。

    二、用泛化物品来理解

    首先得解释一下啥叫泛化物品:一个价值随体积改变而改变的物品,而且对于一个体积 i,有对应的v[i]。

    这个其实人人都见过,只不过没有听说这个名词而已。比如求解01背包就是泛化一个物品的过程,得到的dp[i]就是一个泛化物品。

    还有这么回事,泛化物品的和 :有两个泛化物品G1[i], G2[i],要将这两个物品合并。做法就是对于每一个体积 i ,枚举分配给这两个物品的体积 j ,G[i] = max{G1[j], G2[i - j]}。复杂度O(v2)。

    现在用泛化物品的概念看看树形背包。dp[u][j]表示的是u所在的泛化物品,则从子树向上递归的时候,其实就是不断地将u所在的泛化物品和他的子树vi的泛化物品合并。合并一次的复杂度O(v2),一共n各节点,每合并一次减少一个,所以总复杂度还是O(n * v2)。

    代码和上面完全相同,因为这本来就是对树形背包的两种理解,而不是两种写法。

     1 void dfs(int now)
     2 {
     3   for(int i = head[now]; i; i = e[i].nxt)
     4     {
     5       dfs(e[i].to);
     6       for(int j = m + 1; j; --j)
     7     //倒着枚举,因为左边的dp[now][j]代表新的物品,右边的dp[now][j]是原来的物品
     8     for(int k = 0; k < j; ++k) //枚举分配体积
     9       dp[now][j] = max(dp[now][j], dp[now][j - k] + dp[e[i].to][k]);
    10     }
    11 }

    树形背包O(n * v2)的做法到此也基本讲完了,但这其实都是基础,深入的话还是得靠自己刷题去“悟”。还有一点就是如果哪位大佬会O(nv)的做法,能不能给我讲讲……

  • 相关阅读:
    UVA 11925 Generating Permutations 生成排列 (序列)
    UVA 1611 Crane 起重机 (子问题)
    UVA 11572 Unique snowflakes (滑窗)
    UVA 177 PaperFolding 折纸痕 (分形,递归)
    UVA 11491 Erasing and Winning 奖品的价值 (贪心)
    UVA1610 PartyGame 聚会游戏(细节题)
    UVA 1149 Bin Packing 装箱(贪心)
    topcpder SRM 664 div2 A,B,C BearCheats , BearPlays equalPiles , BearSorts (映射)
    UVA 1442 Cave 洞穴 (贪心+扫描)
    UVA 1609 Foul Play 不公平竞赛 (构(luan)造(gao)+递归)
  • 原文地址:https://www.cnblogs.com/mrclr/p/9894039.html
Copyright © 2011-2022 走看看