背包问题

0/1 背包


 

题目

N 件物品和一个容量为 V 的背包. i 种物品的的费用是 c[i], 价值是 w[i], 每种物品只有一件, 求解将哪些物品装入背包可使价值总和最大

思路

f[i][v] 表示前 i 件物品放到一个容量为 v 的背包可以获得的最大价值

f[i][v] = max( f[i-1][v], f[i][v-c[i]]+w[i] ) 表示第i件物品不放或者放背包两种情况

优化

f[i][j] 是一个 NV列的矩阵, 动态规划的过程就是一行一行的往矩阵里填值

i行仅调用第i-1行的值. i行的第v列调用第i-1行的第v1,v2, 并且v1,v2<=v

基于如上观察, 可对空间优化

for v= V…0

f[v](new) = max(f[v](old), f[v-c[i]](old)+w[i] )

逆序遍历, new 指逻辑上的第i行, old指逻辑上的第i-1

 

 

完全背包问题


 

题目

0/1背包的区别在于每种物品有无穷多件

思路

f[i][v]表示前i件物品放到容量为v的背包可获得的最大价值

f[i][v] = max(f[i-1][v-0*c[i]]+0*w[i], f[i-1][v-1*c[i]]+1*w[i]…f[i-1][v-j*c[i]]+j*w[i]) 枚举第i件物品放入背包的数目, 此公式可递归表示为

f[i][v] = max( f[i][v-c[i]]+w[i], f[i-1][v] )

优化

f[i][j]是一个NV列的矩阵, 动态规划的过程就是一行一行的往矩阵里填值

i行不仅调用第i-1行的值, 还调用第i行的值

for v = 0…V

f[v](new) = max( f[v](old), f[v-c[i]]+w[i](new))

顺序遍历, 保证f[v]在逻辑上是第i-1行的值, f[v'](v'<v)逻辑上是第i行的值

 

 

 

完全背包的一个变形


 

题目

HDOJ 4276

N个节点, i个节点投资 j 分组可获得 value[i][j] 的收益, 求解最大收益

思路

划分的粒度比上面的完全背包更细了些

f[i][v] = max(f[i-1][v-0], f[i-1][v-1]+value[i][1]…f[i-1][0]+value[i][v]) 表示在第i个节点上投资的时间从0->max

优化

for t = 0…T

    for k = 0…t

        f[t] = max(f[t], f[t-k]+value[i][k])

这里多了第二层关于k的循环, 因为不像朴素完全背包问题那样, 物品的价值与个数成正比, 而是, 投资的时间与获得的收益关系由value给出

最外层循环应该是 for t = T...0. 

投资的时间与获得收益关系由 value 给出, 而不是与个数成正比让这道题不能使用递归的方式求解, 必须使用三层循环

使用递归的方式求解只有两层循环, 且t是从小到大. 不使用递归方式求解时有三层循环, t是从大到小. 被划线的代码写成 4 不像了

 

 

 

分组背包问题


 

题目

N件物品和容量为V的背包,i件物品的费用是c[i], 价值是w[i].这些物品被分为若干组, 每组中的物品选择时相互冲突, 最多选择一件. 求解哪些物品放入背包可使获得的总价值最大

思路

对组遍历, 枚举组中的物品选取1件或者一件都不选取

f[k][v] = max(f[k-1][v], f[k-1][v-c[i]+w[i]]) i为第k组的某一件物品

算法

for 所有的组

    for v= V…0

        for 所有的i属于组k

            f[v] = max(f[v], f[v-c[i]+w[i]])

 

 

 

三维背包


 

题目

九度棋盘寻宝和棋盘寻宝扩展

8*8 的棋盘, 64个格子上放在礼物, 每个礼物有一定的价值. 人的初始位置在左上角, 求解此人走到右下角时能拿到的不超过limit的最大价值, 人每次可以选择向下走或向右走

思路

i行第j列的礼物价值为 w[i][j], 代价为c[i][j], w[i][j] ==c[i][j]

f[i][j][v]表示走到第i行第j列时不超过limit的最大价值

那么f[i][j][v] = max(f[i-1][j][v-c[i][j]], f[i][j][v-c[i][j]])+w[i][j])

代码

for(int i = cl-1; i >= 0; i --) {

        for(int j = cl-1; j >= 0; j--) {

            for(int k = 0; k <= limit; k ++) {

                if(i+1 == cl && j+1 ==cl) break;

                right = getNeighbor(i, j+1, k-chessboard[i][j]);

                down = getNeighbor(i+1, j, k-chessboard[i][j]);

                result[i][j][k] = max(right, down) + chessboard[i][j];

            }

        }

    }

 

 

 

BTW


 

  1. 空间的优化看着舒服, 想的时候要多绕一道弯, 我做题时, 实际上都不进行空间压缩. 曾在course 上做25个点的TSP, 那就是那道题是非优化空间不可的
  2. 上面写"i行调用第i-1"行的值. 这里调用往往有主动request的感觉, 而在实现floydwarshall 算法时感受到了push的效果, 即第i-1行去主动更新第i
  3. 研一上算法课. 近似算法也讲到了背包问题的求解, 当时还很疑惑, 为什么一个被完美解决的题目还要研究近似算法. 问了下老师发才了解到上面的背包问题有一个共性, 容量不会太大, 且容量往往为整数, 这可以理解成是比较理想的情况, 现实生活中的背包问题, 往往没有这么强的假设
  4. DP 遍历时, 最外层的变量应该是独立的, 正逆序一定要搞清楚. 完全背包代码中的递归求解与正常的三重循环通过HDOJ4276 有了新的认识
posted @ 2013-11-15 21:32  SangS  阅读(498)  评论(0编辑  收藏  举报