0-1背包

  • 描述:N件物品,第i件的重量是w[i],价值v[i]。有一个容量为W的背包,求将哪些物品放入背包可使总价值最大。每件物品可以用0或1次
  • 分析:根据题意,可以写出表达式:

\[max(\Sigma v_ix_i), s.t. \Sigma w_ix_i<=W, x_i\in\{0, 1\}
\]

最直接的思路就是:对于每件物品,都有yes/no两种选择,尝试所有的组合,记录每个组合的价值,选出满足重量条件的最大价值。时间复杂度\(O(2^n)\),空间复杂度\(O(n)\)。

// backtracking
class knapsack01 {
public:
int knapsack(int W, vector<int>& w, vector<int>& v, string& ans) {
string cur(w.size(), '0'); dfs(0, 0, 0, W, w, v, cur, ans);
return maxV;
}
private:
void dfs(int s, int curW, int curV, int W, vector<int>& w, vector<int>& v, string& cur, string& ans) {
// 到达叶子结点,得到一个解,所以在这里更改最终结果
if (s >= w.size()) {
if (maxV < curV) {
ans.assign(cur);
maxV = curV;
}
return;
} // as for goods s, two choices
for (int i = 0; i < 2; ++i) {
cur[s] = i + '0'; if (curW + i * w[s] <= W) {
curW += i * w[s];
curV += i * v[s];
dfs(s + 1, curW, curV, W, w, v, cur, ans);
curW -= i * w[s];
curV -= i * v[s];
}
}
} int maxV = 0;
};

上面的程序可以通过剪枝进行优化,下来换一种思路:

dp[i][j]表示前i件物品重量恰好为j时具有的最大价值,问题转化为求dp[N][0...W]的最大值,边界条件dp[0...N][0]=0

假设3件物品,\(w=\{1,1,2\}\),\(v=\{1,2,4\}\),\(W=2\),先用递归形式分析,每件物品只有yes/no两种状态:



可以看到,求解过程中有很多重叠子问题,故可以采用记忆化递归求解,时间复杂度即为子问题数量\(O(NW)\),空间复杂度\(O(NW)\)。

记忆化递归可以写成自底向上的动态规划,状态转移方程:

\[dp[i][j]=max\{dp[i-1][j], v[i]+dp[i-1][j-w[i]]\}
\]

// dp->space complexity O(NW)
class knapsack01 {
public:
int knapsack(int W, vector<int>& w, vector<int>& v) {
const int N = w.size();
vector<vector<int>> dp(N + 1, vector<int>(W + 1, 0));
for(int i = 1;i <= N;++i)
for (int j = w[i - 1]; j <= W; ++j) {
dp[i][j] = max(dp[i - 1][j], v[i - 1] + dp[i - 1][j - w[i - 1]]);
} return *max_element(dp[N].begin(), dp[N].end());
}
};

前i件物品只依赖于前i-1件物品,\(dp\)数组的更新方向为:



所以可以使用滚动数组降低空间复杂度为\(O(W)\):

// dp->space complexity O(W)
// method 1: use temp array
class knapsack01 {
public:
int knapsack(int W, vector<int>& w, vector<int>& v) {
const int N = w.size();
vector<int> dp(W + 1, 0);
for (int i = 1; i <= N; ++i) {
vector<int> temp(W + 1, 0);
for (int j = w[i - 1]; j <= W; ++j) {
temp[j] = max(temp[j], v[i - 1] + dp[j - w[i - 1]]);
}
dp.swap(temp);
} return *max_element(dp.begin(), dp.end());
}
}; // method 2: use scrolling array
class knapsack01 {
public:
int knapsack(int W, vector<int>& w, vector<int>& v) {
const int N = w.size();
vector<int> dp(W + 1, 0);
for (int i = 1; i <= N; ++i) {
// iterate j reversely, avoid dp override
for (int j = W; j >= w[i - 1]; --j) {
dp[j] = max(dp[j], v[i - 1] + dp[j - w[i - 1]]);
}
} return *max_element(dp.begin(), dp.end());
}
};

完全背包

  • 每件物品可以使用任意多次
  • 一个Naive的思路: 虽然题目描述每件物品可以使用任意多次,但实际上由于W的限制,每件物品最多使用\(\lfloor W/w[i] \rfloor\)次。这样我们可以将每件物品拆为\(\lfloor W/w[i] \rfloor\)件,问题就转化为了0-1背包。子问题仍然有NW个,但是求解每个子问题需要\(O(W/w[i])\),总的时间复杂度\(O(\Sigma (W/w[i])*W)\),也即\(O(W*拆后物品件数)\)。
  • 更tricky的做法:W无法改变,只能改变拆后物品件数。这里可以使用二进制的思想:假设我们某件物品可以使用\(10=8+2\)次,原本需要复制出10件,现在只要复制出2件,价值和重量是原来的8倍和2倍,这样就降低了复杂度。
  • 完全背包有\(O(NW)\)的算法。

多重背包

  • 每件物品最多可以使用\(num[i]\)次。
  • 同样,Naive的思路就是将每件物品都复制\(num[i]\)次,问题转化为0-1背包,复杂度\(O(\Sigma nums[i]*W)\)。
  • 将\(num[i]\)用二进制表示,价值和重量变为原来的相应倍,降低复杂度。

Future

后续还有混合背包、二维费用的背包等,详情可以学习背包九讲

Knapsack Problem的更多相关文章

  1. knapsack problem 背包问题 贪婪算法GA

    knapsack problem 背包问题贪婪算法GA 给点n个物品,第j个物品的重量,价值,背包的容量为.应选哪些物品放入包内使物品总价值最大? 规划模型 max s.t. 贪婪算法(GA) 1.按 ...

  2. FZU 2214 Knapsack problem 01背包变形

    题目链接:Knapsack problem 大意:给出T组测试数据,每组给出n个物品和最大容量w.然后依次给出n个物品的价值和体积. 问,最多能盛的物品价值和是多少? 思路:01背包变形,因为w太大, ...

  3. [DP] The 0-1 knapsack problem

    Give a dynamic-programming solution to the 0-1 knapsack problem that runs in O(nW) time, where n is ...

  4. 对背包问题(Knapsack Problem)的算法探究

    对背包问题(Knapsack Problem)的算法探究 至繁归于至简,这次自己仍然用尽可能易理解和阅读的解决方式. 1.问题说明: 假设有一个背包的负重最多可达8公斤,而希望在背包中装入负重范围内可 ...

  5. 动态规划法(四)0-1背包问题(0-1 Knapsack Problem)

      继续讲故事~~   转眼我们的主人公丁丁就要离开自己的家乡,去大城市见世面了.这天晚上,妈妈正在耐心地帮丁丁收拾行李.家里有个最大能承受20kg的袋子,可是妈妈却有很多东西想装袋子里,已知行李的编 ...

  6. FZU 2214 ——Knapsack problem——————【01背包的超大背包】

    2214 Knapsack problem Accept: 6    Submit: 9Time Limit: 3000 mSec    Memory Limit : 32768 KB  Proble ...

  7. FZU-2214 Knapsack problem(DP使用)

    Problem 2214 Knapsack problem Accept: 863    Submit: 3347Time Limit: 3000 mSec    Memory Limit : 327 ...

  8. 0/1 knapsack problem

    Problem statement Given n items with size Ai and value Vi, and a backpack with size m. What's the ma ...

  9. FZU - 2214 Knapsack problem 01背包逆思维

    Knapsack problem Given a set of n items, each with a weight w[i] and a value v[i], determine a way t ...

  10. (01背包 当容量特别大的时候) Knapsack problem (fzu 2214)

    http://acm.fzu.edu.cn/problem.php?pid=2214   Problem Description Given a set of n items, each with a ...

随机推荐

  1. Linux基础篇,Shell

    一.基本知识 Shell Script是一种脚本.可以用来极大的简化计算机的管理.在谱写shell script的时候,需要注意以下问题: 1. 指令的执行是从上而下. 从左而右的分析与执行: 2. ...

  2. 接口请求:get、post (requests方法)

    https://www.cnblogs.com/lanyinhao/p/9634742.html 比较全面 1.模块说明 requests是使用Apache2 licensed 许可证的HTTP库. ...

  3. js及jquery常用插件

    1.backstretch背景图片插件 可实现背景自适应效果 <script src="dist/js/lib/backstretch/jquery.backstretch.min.j ...

  4. AJ学IOS(20)UI之UIPickerView_点菜系统

    AJ分享,必须精品 先看效果图 ## UIPickerView控件 UIPickerView用处: 用来展示很多行(row) 很多列(component )的数据,多用于电子商务的点菜,城市选择等等. ...

  5. AJ学IOS(15)UI之曾经大热的打砖块小游戏

    AJ分享,必须精品 先看效果图 代码 // ViewController.m // 打砖块 // // Created by liufan on 13-8-17. // Copyright (c) 2 ...

  6. mvc传递json数据到view简单实例

    基于extjs4.2 controller //存储数据的类 public class DataLink { public string Name { get; set; } public strin ...

  7. Java读源码之CountDownLatch

    前言 相信大家都挺熟悉 CountDownLatch 的,顾名思义就是一个栅栏,其主要作用是多线程环境下,让多个线程在栅栏门口等待,所有线程到齐后,栅栏打开程序继续执行. 案例 用一个最简单的案例引出 ...

  8. PHP函数:json_last_error

    json_last_error()  - 返回 JSON 编码解码时最后发生的错误.. 说明: json_last_error ( void ) : int 参数: 无 返回值: 返回一个整型(int ...

  9. 反向icmp_shell

    前言 很老的一个技术了,学习下. ICMP协议工作方式简介 Internet控制报文协议(ICMP)是Internet协议族中一个.它被用于包括路由器在内的网络设备中,用来发送错误报文和操作信息,表示 ...

  10. shiro:加密及密码比对器(三)

    基于[自定义remle(二)]项目增加加密功能 1:数据库t_user表增加一列(盐) 增加字段:salt CREATE TABLE `t_user` ( `id` int(11) NOT NULL ...