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. 给listview添加长时间点击事件(完成删除操作)

    出现的问题是:当长时间点击listview的时候,触发长时间点击事件的同时也会触发点击事件,处理的办法是在长时间点击事件中加上return true:这样就可以很好的解决了. 给listview天机长 ...

  2. C语言 文件操作(二)

    1.fputc(); fputc 是 file output char 的所以,意思是向指定的文件中写入一个字符.fputc() 的用法为: int fputc ( int ch, FILE *fp ...

  3. Flask 入门(三)

    官方的文档虽然正规,但是有点太过书面语,有时候,明明很简单的一个程序,如果非要看它的说明,反而会让人疑惑不解,倒不如看一下别人写的简单的一个demo,jinjia2模板看官方的文档看了5回,愣是不明白 ...

  4. Boyer-Moore字符串搜索(BM算法)的Python实现

    BM算法根据两个判据来进行字符串匹配,分别是“坏字符规则”和‘好后缀规则",其中好后缀规则可以单独使用,算法的图解可以参照下面这篇博文: https://www.cnblogs.com/wx ...

  5. Python操作rabbitmq系列(三):多个接收端消费消息

    接着上一章.这一章,我们要将同一个消息发给多个客户端.这就是发布订阅模式.直接看代码: 发送端: import pikaimport sys connection = pika.BlockingCon ...

  6. 动态规划_基础_最长公共子序列_多种方法_递归/dp

    D: 魔法少女资格面试 题目描述 众所周知,魔法少女是一个低危高薪职业.随着近年来报考魔法少女的孩子们越来越多,魔法少女行业已经出现饱和现象!为了缓和魔法少女界的就业压力,魔法少女考核员丁丁妹决定增加 ...

  7. Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(十四)之Type Information

    Runtime type information (RTTI) allow you to discover and use type information while a program is ru ...

  8. GeoGebra案例(傅里叶级数的方波)

    傅里叶级数介绍:请移步参见这位马大佬的博文 https://www.matongxue.com/madocs/619.html 马大佬中的gif图在我看来是非常直观且有趣的,奈何本人本领实在有限,学习 ...

  9. sql 自增序列

    一.使用set identity_insert [database][owner][table]on设置时,要在插入语句中显示列出插入的列;

  10. Netty 中的异步编程 Future 和 Promise

    Netty 中大量 I/O 操作都是异步执行,本篇博文来聊聊 Netty 中的异步编程. Java Future 提供的异步模型 JDK 5 引入了 Future 模式.Future 接口是 Java ...