背包问题
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] 是一个 N行V列的矩阵, 动态规划的过程就是一行一行的往矩阵里填值
第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]是一个N行V列的矩阵, 动态规划的过程就是一行一行的往矩阵里填值
第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
- 空间的优化看着舒服, 想的时候要多绕一道弯, 我做题时, 实际上都不进行空间压缩. 曾在course 上做25个点的TSP, 那就是那道题是非优化空间不可的
- 上面写"第i行调用第i-1"行的值. 这里调用往往有主动request的感觉, 而在实现floydwarshall 算法时感受到了push的效果, 即第i-1行去主动更新第i行
- 研一上算法课. 近似算法也讲到了背包问题的求解, 当时还很疑惑, 为什么一个被完美解决的题目还要研究近似算法. 问了下老师发才了解到上面的背包问题有一个共性, 容量不会太大, 且容量往往为整数, 这可以理解成是比较理想的情况, 现实生活中的背包问题, 往往没有这么强的假设
- DP 遍历时, 最外层的变量应该是独立的, 正逆序一定要搞清楚. 完全背包代码中的递归求解与正常的三重循环通过HDOJ4276 有了新的认识