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. 搭建DVWA Web渗透测试靶场

    文章更新于:2020-04-13 按照惯例,需要的文件附上链接放在文首. 文件名:DVWA-1.9-2020.zip 文件大小:1.3 M 文件说明:这个是新版 v1.9 (其实是 v1.10开发版) ...

  2. 尝试用tornado部署django

    import os from tornado.options import options, define from tornado import httpserver from tornado.io ...

  3. Java并发编程实战 01并发编程的Bug源头

    摘要 编写正确的并发程序对我来说是一件极其困难的事情,由于知识不足,只知道synchronized这个修饰符进行同步. 本文为学习极客时间:Java并发编程实战 01的总结,文章取图也是来自于该文章 ...

  4. 2017蓝桥杯购物单(C++B组)

    原题: 标题: 购物单 小明刚刚找到工作,老板人很好,只是老板夫人很爱购物.老板忙的时候经常让小明帮忙到商场代为购物.小明很厌烦,但又不好推辞.这不,XX大促销又来了!老板夫人开出了长长的购物单,都是 ...

  5. istream_iterator && istream_iteratorbuf

    注意 读字符时, std::istream_iterator 默认跳过空白符(除非用 std::noskipws 或等价物禁用,而 std::istreambuf_iterator 不跳过.另外, s ...

  6. 项目中你不得不知的11个Java第三方类库

    项目中你不得不知的11个Java第三方类库 博客分类: Java综合 JavaGoogle框架单元测试Hibernate Java第三方library ecosystem是一个很广阔的范畴.不久前有人 ...

  7. 新时代前端必备神器 Snapjs之弹动效果

    有人说不会 SVG 的前端开发者不叫开发者,而叫爱好者.前端不光是 Angularjs 了,这时候再不学 SVG 就晚了!(如果你只会 jQuery 就当我没说...)这里我就给大家分享一个前几天在别 ...

  8. 掉了10根头发都无法解决的数学题,python帮你完美解答

    本来这个周末过得开开心心,结果为了解一道数学题薅掉了一把头发...整整10根! 而且还是一道小学数学题!!! 到底是什么题呢?大家看看吧 这不就是一道逻辑题嘛! 先假如丁错,则甲乙丙对,此时最小的ab ...

  9. 【Jenkins】参数化引用

    我们在Jenkins里设置了参数如下 1. Jenkins中引用 shell引用  $env windows bat引用 %env% 在git等源码管理时,调用参数的格式${env} 2. jmete ...

  10. Python套接字之UDP

    目录 基于UDP的socket 发送消息 接收消息 基于UDP的socket 面向无连接的不可靠数据传输,可以没有服务器端,只不过没有服务器端,发送的数据会被直接丢弃,并不能到达服务器端 发送消息 在 ...