AcWing 2. 01背包问题

#include <iostream>
#include <algorithm> using namespace std; const int N = 1010; int n, m;
int v[N], w[N];
int f[N]; int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i++) cin >> v[i] >> w[i]; for(int i = 1; i <= n; i++)
{
for(int j = m; j >= v[i]; j--)
{
f[j] = max(f[j], f[j-v[i]] + w[i]);
}
} cout << f[m] << endl; return 0;
}

AcWing 3. 完全背包问题

状态表示:\(f(i,j)\) 其中的\(i\)表示只从前\(i\)种物品选,\(j\)表示选出来的物品总体积 \(\leq\) j,因此\(f(i,j)\)就表示从前\(i\)种物品中选出总体积不超过\(j\)的所有选法。

状态计算:集合划分

第\(i\)种物品选\(0\),\(1\),\(2\),\(3\),\(……\),\(k-1\),\(k\)个。

如果第\(i\)种物品,一个也不选,则说明我们只考虑前\(i-1\)种物品,即:\(f(i-1, j)\)

如果选第\(i\)种物品选\(k\)个,则我们可以采取与\(01\)背包类似的思路,去掉已经确定了价值的\(k\)个第\(i\)种物品,因为这并不会影响前\(i-1\)种物品我们选法的最大价值,因此得到方程:

\[f(i, j) = f(i-1, j - v[i] * k) + w[i] * k
\]

这里注意,第\(i\)种物品如果我们不选的话,就等价于\(f(i-1, j)\),因此不选的情况我们也可以并到上式子中,因此我们得到状态转移方程:

\[f(i, j) = max(f(i-1, j), f(i-1, j - v[i] * k) + w[i] * k)
\]

再进行优化:

将式子展开:

\[f(i, j) = max(f(i-1, j), f(i-1, j-v) + w, f(i-1, j-2*v) + 2*w, f(i-1, j-3*v) + 3*w, ...)
\]

而:

\[f(i, j-v) = max( f(i-1, j - v) , f(i-1, j-2*v) + w, f(i-1, j-3*v) + 2*w, ...)
\]

观察上下两式,我们看出式子\(1\)后 \(f(i-1, j-v) + w, f(i-1, j-2*v) + 2*w, f(i-1, j-3*v) + 3*w, ...\)可以由式子\(2\):\(f(i, j-v)\) \(+\) \(w\)得到,于是我们就可以进行合并,我们可以得到这样的式子:

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

因此这样就可以不用再枚举\(k\)了,只需要枚举两个状态即可。

\(f(i, j) = max(f(i-1, j), f(i, j-v) + w)\)与\(01\)背包的状态转移方程并不同,\(01\)背包需要从\(i-1\)转移而来,而完全背包是从\(i\)转移而来。

#include <iostream>
#include <algorithm> using namespace std; const int N = 1010;
int n, m;
int f[N], w[N], v[N]; int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> v[i] >> w[i];
} for (int i = 1; i <= n; i++) {
for (int j = v[i]; j <= m; j++) {
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
} cout << f[m] << endl;
return 0;
}

AcWing 4. 多重背包问题 1

#include <iostream>
#include <algorithm> using namespace std; const int N = 110;
int n, m;
int f[N][N];
int v[N], w[N], s[N]; int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> v[i] >> w[i] >> s[i];
} for (int i = 1; i <= n; i++) {
for (int j = 0; j <= m; j++) {
for (int k = 0; k <= s[i] && k * v[i] <= j; k++) {
f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
}
}
}
cout << f[n][m] << endl;
return 0;
}

AcWing 5. 多重背包问题 II

二进制优化

这个如果再用朴素做法就不行了,\(O(NVS)\) 大约要\(4*10^9\),肯定不行,而若我们对\(S\)进行二进制拆开然后将整个问题当成\(01\)背包来做,那么复杂度就被有优化为了\(O(NVlogS)\) 大约为\(2*10^7\),优化了两个数量级。

那为什么我们不用优化完全背包的方法来优化多重背包呢?先从状态转移方程出发

\[f[i][j] = max(f[i-1][j], f[i-1][j-v]+w, f[i-1][j-2*v]+2*w, ... ,f[i-1][j-s*v]+s*w) \tag{1}
\]

而:

\[f[i][j-v] = max(f[i-1][j-v], f[i-1][j-2*v]+w, ... ,f[i-1][j-s*v]+(s-1)*w, f[i-1][j-(s+1)*v]+s*w) \tag{2}
\]

可见\(2\)式比\(1\)式多了一项,因此不能这样优化。

进入二进制优化正题

假设有件物品的数量为\(200\),那么我们可以分成\(1, 2, 4, 8, 16, 32, 64\)这么几组,还剩下一个\(73\)不再拆分,分成的每一组我们最多只能拿一次那么就可以按照\(01\)背包做了。

那会出现什么问题吗,例如我们想拿3件但是并没有啊,这问题并不会出现,\(1 + 2 = 3\)啊, 对于例子,\(1,2\)可以凑出\(1-3\)的任何一个数字,同理\(1, 2\)加一个4就可以凑出\(1-7\)任何一个数字,再同理,假设我们可以把\(n\)分出一组\(1, 2, ..., 2^k, C\),\(C\)不足以\(2^{k+1}\),否则我们就是\(1, 2, ..., 2^k, 2^{k+1}\),我们可以用\(1, 2, ..., 2^k\)凑出\(2^{k+1} - 1\)(根据等差数列求和)中的任何一个数字,那么再加一个\(C\)就可以凑出\(1-n\)中任何一个数。那么这样再做一遍\(01\)背包即可,复杂度就由\(O(NVS)\)优化到了\(O(NVlogS)\)。

#include <iostream>
#include <algorithm> using namespace std; const int N = 12000;
int v[N], w[N];
int n, m, f[N]; int main() {
cin >> n >> m;
int cnt = 0;
for (int i = 1; i <= n; i++) {
int a, b, s;
cin >> a >> b >> s;
int k = 1;
while (k <= s) {
cnt++;
v[cnt] = a * k, w[cnt] = b * k; //每个物品a 价值为b
s -= k;
k *= 2;
}
if (s > 0) {
cnt++;
v[cnt] = a * s, w[cnt] = b * s;
}
}
n = cnt;
for (int i = 1; i <= n; i++) {
for (int j = m; j >= v[i]; j--) {
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
cout << f[m] << endl;
return 0;
}

AcWing 6. 多重背包问题 III

留个坑 等我学了单调队列DP 再来填。

AcWing 7. 混合背包问题

分开分析即可。

#include <bits/stdc++.h>

using namespace std;

const int N = 1010;
int f[N];
int n, m; int main() {
cin >> n >> m;
for (int i = 0; i < n; i++) {
int v, w, s;
cin >> v >> w >> s;
if (s == 0) {
for (int j = v; j <= m; j++) f[j] = max(f[j], f[j - v] + w);
} else {
if (s == -1) s = 1;
for (int k = 1; k <= s; k *= 2) {
for (int j = m; j >= k * v; j--) {
f[j] = max(f[j], f[j - k * v] + k * w);
}
s -= k;
}
if (s) {
for (int j = m; j >= s * v; j--) {
f[j] = max(f[j], f[j - s * v] + s * w);
}
}
}
} cout << f[m] << endl; return 0;
}

AcWing 8. 二维费用的背包问题

在01背包基础上加了一维。

#include <iostream>
#include <cstring>
#include <algorithm> using namespace std; const int N = 110;
int n, V1, V2;
int f[N][N]; int main() {
cin >> n >> V1 >> V2;
for (int i = 1; i <= n; i++) {
int v1, v2, w;
cin >> v1 >> v2 >> w;
for (int j = V1; j >= v1; j--) {
for (int k = V2; k >= v2; k--) {
f[j][k] = max(f[j][k], f[j - v1][k - v2] + w);
}
}
} cout << f[V1][V2] << endl; return 0;
}

AcWing 9. 分组背包问题

#include <iostream>

using namespace std;

const int N = 110;
int s[N], w[N][N], v[N][N];
int n, m, f[N]; int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> s[i];
for (int j = 0; j < s[i]; j++) {
cin >> v[i][j] >> w[i][j];
}
} for (int i = 1; i <= n; i++) {
for (int j = m; j >= 0; j--) {
for (int k = 0; k < s[i]; k++) { //枚举有多少个
if (v[i][k] <= j) {
f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
}
}
}
}
cout << f[m] << endl;
return 0;
}

AcWing 10. 有依赖的背包问题

#include <iostream>
#include <algorithm>
#include <cstring> using namespace std; const int N = 110;
int f[N][N]; //表示从以u根的子树中选,总体积不超过j的最大价值
int h[N], e[N], ne[N], idx;
int n, m;
int v[N], w[N]; void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
} void dfs(int u) {
for (int i = h[u]; i != -1; i = ne[i]) {
int son = e[i];
dfs(e[i]); for (int j = m - v[u]; j >= 0; j--) { //左一遍分组背包
for (int k = 0; k <= j; k++) {
f[u][j] = max(f[u][j], f[u][j - k] + f[son][k]);
}
}
}
for (int i = m; i >= v[u]; i--) f[u][i] = f[u][i - v[u]] + w[u]; //选择v[u]
for (int i = 0; i < v[u]; i++) f[u][i] = 0;
} int main() {
cin >> n >> m;
int root;
memset(h, -1, sizeof h);
for (int i = 1; i <= n; i++) {
int p;
cin >> v[i] >> w[i] >> p;
if (p == -1) root = i;
else add(p, i);
} dfs(root); cout << f[root][m] << endl; return 0;
}

AcWing 11. 背包问题求方案数

类似于最短路的计数问题。

#include <iostream>
#include <algorithm> using namespace std; const int N = 1010, Mod = 1e9 + 7;;
int f[N], cnt[N];
int n, m; int main() {
cin >> n >> m;
for (int i = 0; i <= m; i++) cnt[i] = 1; for (int i = 1; i <= n; i++) {
int v, w;
cin >> v >> w;
for (int j = m; j >= v; j--) {
int x = f[j - v] + w;
if (x > f[j]) {
f[j] = x;
cnt[j] = cnt[j - v];
} else if (x == f[j]) {
cnt[j] = (cnt[j - v] + cnt[j]) % Mod;
}
}
} cout << cnt[m] << endl; return 0;
}

AcWing 12. 背包问题求具体方案

对于方案的求解,我们可以类似于迷宫求最短路径时候的记录方案。

就是判断一下每一步是从哪一步转移过去的,对于背包来说,f[i][j]可能由两个状态转移过来:

  1. 如果f[i][j] == f[i - 1][j]:表示从不选第i个物品转移到了f[i][j],那么记录转移状态的时候肯定必选第i个物品
  2. 如果f[i][j] == f[i - 1][j - v] + w:表示选第i个物品转移到了f[i][j],那么记录路径的时候肯定必然不能选第i个物品。
  3. 还有一种特例,f[i - 1][j] == f[i - 1][j - v] + w表示第i个物品可选可不选都可以转移到f[i][j],那么此是我们肯定是要选的,因为题目中还要保证字典序最小,所以选上肯定比不选的字典序要小。

综上,能选必选。

还有就是对于我们求转移状态的时候其实是倒着往前求得,类比于迷宫,而我们要获得字典序最小需要从前往后求,那么我们解决办法就是再求f的时候直接从ni枚举即可。

#include <iostream>
#include <cstring>
#include <algorithm> using namespace std; const int N = 1010; int n, m;
int f[N][N];
int v[N], w[N]; int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> v[i] >> w[i]; for (int i = n; i >= 1; i--) {
for (int j = 0; j <= m; j++) {
f[i][j] = f[i + 1][j];
if (j >= v[i]) {
f[i][j] = max(f[i][j], f[i + 1][j - v[i]] + w[i]);
}
}
} int j = m;
for (int i = 1; i <= n; i++) {
if (j >= v[i] && f[i][j] == f[i + 1][j - v[i]] + w[i]) {
cout << i << " ";
j -= v[i];
}
} return 0;
}

关于记录方案还有另外一种方式:

这种转移方法是来自迷宫求最短路的记录方案的灵感,这个题里y总只是提了一下,没有具体讲。

开一个PII g[i][j]数组来记录,表示first表示(i, j)是由first这个点转移而来,second表示由first转移而来的时候第first物品是否被选.

g[i][j] = {i + 1, false}:表示(i,j)这个状态是从没选第i + 1个物品转移而来。

g[i][j] = {i + 1, true}:表示(i,j)这个状态是从选第i + 1个物品转移而来。

然后边DP边记录即可。

对于输出的过程:首先要判断一下g[i][j].second是否为true如果为true,那么表示是由选择一个物品转移而来,那么当前体积需要跟着变化,那就是减去当前的v,反之如果为false,那么就不必减去当前的v,还有注意这两种情况下i都需要进行转移。

还要注意的就是求解的时候还是要倒序求,因为需要保证字典序最小,又因为这个WA了一发。

int i = 1, j = m;
while (i <= n && j) {
if (g[i][j].second == true) {
int t = i;
cout << i << " ";
i = g[i][j].first;
j -= v[t];
} else {
i = g[i][j].first;
}
}

详细的代码有一些细节都在注释里了:

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector> using namespace std; typedef pair<int, bool> PII;
const int N = 1010; int n, m;
int f[N][N];
int v[N], w[N];
PII g[N][N]; //first表示第几件物品 second表示选没选
vector<int> res; int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> v[i] >> w[i]; for (int i = n; i >= 1; i--) {
for (int j = 0; j <= m; j++) {
f[i][j] = f[i + 1][j];
g[i][j] = {i + 1, false};
if (j >= v[i]) {
if (f[i + 1][j - v[i]] + w[i] >= f[i][j]) { //注意这里一定要大于等于,因为为了保证字典序,能选必选
g[i][j] = {i + 1, true};
f[i][j] = f[i + 1][j - v[i]] + w[i];
}
}
}
} int i = 1, j = m;
while (i <= n && j) { //这里一定是&& 一开始写成了||,调了半个多小时
if (g[i][j].second == true) {
int t = i; //这里一定要临时存一下i,因为下边i接着变成了它的上一个状态,而j要减去的是当前状态的v
cout << i << " ";
i = g[i][j].first;
j -= v[t];
} else {
i = g[i][j].first;
}
} return 0;
}

背包DP全类型的更多相关文章

  1. 背包dp整理

    01背包 动态规划是一种高效的算法.在数学和计算机科学中,是一种将复杂问题的分成多个简单的小问题思想 ---- 分而治之.因此我们使用动态规划的时候,原问题必须是重叠的子问题.运用动态规划设计的算法比 ...

  2. 树形DP和状压DP和背包DP

    树形DP和状压DP和背包DP 树形\(DP\)和状压\(DP\)虽然在\(NOIp\)中考的不多,但是仍然是一个比较常用的算法,因此学好这两个\(DP\)也是很重要的.而背包\(DP\)虽然以前考的次 ...

  3. luogu 4377 Talent show 01分数规划+背包dp

    01分数规划+背包dp 将分式下面的部分向右边挪过去,通过二分答案验证, 注意二分答案中如果验证的mid是int那么l=mid+1,r=mid-1,double类型中r=mid,l=mid; 背包dp ...

  4. 【bzoj1495】[NOI2006]网络收费 暴力+树形背包dp

    题目描述 给出一个有 $2^n$ 个叶子节点的完全二叉树.每个叶子节点可以选择黑白两种颜色. 对于每个非叶子节点左子树中的叶子节点 $i$ 和右子树中的叶子节点 $j$ :如果 $i$ 和 $j$ 的 ...

  5. 【洛谷】P1541 乌龟棋(四维背包dp)

    题目背景 小明过生日的时候,爸爸送给他一副乌龟棋当作礼物. 题目描述 乌龟棋的棋盘是一行N个格子,每个格子上一个分数(非负整数).棋盘第1格是唯一的起点,第N格是终点,游戏要求玩家控制一个乌龟棋子从起 ...

  6. 算法复习——背包dp

    1.01背包 二维递推式子: 代码: ;i<=n;i++) ;x--) ][x-w[i]]+c[i],f[i-][x]); ][x]; printf("%d",f[n][m] ...

  7. Codeforces 922 E Birds (背包dp)被define坑了的一题

    网页链接:点击打开链接 Apart from plush toys, Imp is a huge fan of little yellow birds! To summon birds, Imp ne ...

  8. hdu 5534 Partial Tree 背包DP

    Partial Tree Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://acm.hdu.edu.cn/showproblem.php?pid= ...

  9. HDU 5501 The Highest Mark 背包dp

    The Highest Mark Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://acm.hdu.edu.cn/showproblem.php?p ...

  10. Codeforces Codeforces Round #319 (Div. 2) B. Modulo Sum 背包dp

    B. Modulo Sum Time Limit: 1 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/contest/577/problem/ ...

随机推荐

  1. 语音合成技术汇总1:Glow-TTS:通过单调对齐实现文本到语音的生成流

    今天开始开一期语音合成经典论文的翻译 Glow-TTS:通过单调对齐实现文本到语音的生成流 摘要: 最近,文本到语音(Text-to-Speech,TTS)模型,如FastSpeech和ParaNet ...

  2. 关于自定义程序打包成jar包,并读取配置

    前言 在实际开发过程中,我们有时候有把你编写的一段程序打成jar包的需求,而一些配置是需要去配置文件里面读取关于这项目的一些配置,本人在网络上查询了众多的资料,总的来说可以归为3类 1.从数据库读取配 ...

  3. python3使用ESL和sipp自动多轮压测FreeSWITCH

    环境:CentOS 7.6_x64   FreeSWITCH版本 :1.10.9   sipp版本:3.6.1   python版本:3.9.12 日常工作中,有时会遇到批量自动压测FreeSWITC ...

  4. Redis从入门到放弃(6):持久化

    1.引言 Redis作为一种高性能的内存数据存储系统,常被用作缓存.会话存储.消息队列等多种应用场景.然而,由于其数据存储在内存中,一旦发生意外或服务器重启,数据就会丢失.为了保障数据的持久性和安全性 ...

  5. centos7升级内核到最新稳定版

    前言 centos7默认的内核版本才3.10,诸如VXLAN.eBPF等特性无法体验,因此需要升级.目前(2022.02)Linux的内核版本已更新到5.16. 步骤 更新仓库 yum update ...

  6. Linux 过滤进程和端口号

    IDEA覆盖率测速显示百分比 ctrl + alt + F6 取消勾选 ps - ef | grep java过滤Java进程 netstat -anop | grep 74933 过滤端口号 重命名 ...

  7. 解放双手!ChatGPT助力编写JAVA框架

    亲爱的Javaer们,在平时编码的过程中,你是否曾想过编写一个Java框架去为开发提效?但是要么编写框架时感觉无从下手,不知道从哪开始.要么有思路了后对某个功能实现的技术细节不了解,空有想法而无法实现 ...

  8. Badusb制作,远程别人电脑

    Badusb制作 插一下U盘黑一台电脑,插了我的U盘你可就是我的脑了,(▽) 理论准备 我们要用它就应该知道他的工作原理是怎么样的,方便我们去发散思维去使用它. Badusb的原理是利用HID(Hum ...

  9. H.265+SRS6.0服务器部署

    H.265+SRS6.0服务器部署 SRS从6.0开始,全面支持H.265,包括RTMP.FLV.HLS.GB28181.WebRTC等等.具体的服务器部署及H.265推流步骤如下. 1. SRS 要 ...

  10. API接口开发管理平台--多领域企业数字化管理解决方案

    随着数字化时代的到来,企业需要进行数字化转型才能更好地适应市场需求和用户需求.而API接口则是数字化转型中的重要组成部分,可以帮助企业更好地管理信息,提高效率.本文将介绍挖数据解决方案--API接口开 ...