背包

news/发布时间2024/5/19 3:41:00

一般代码只是例子,具体使用依据题目来, DP是一种思想,代码都以属性为最大值等等为例子

01背包

基本的背包
简单说就是有n个物品和容量为m的包,求其max/min/方案数等等即属性
一般转移方程为f[i][j]意思为在前i个里容量为j的情况下的要求的属性
(可忽略)一般这里的转移是在f[i][j],第i个数取与不取
时间复杂度O(n*m)

一般代码

for (int i = 1; i <= n; i ++ ) // 枚举当前几个物品{int v, w;cin >> v >> w;for (int j = 1; j <= m; j ++ ){f[i][j] = f[i - 1][j];if (j >= v) f[i][j] = max(f[i][j], f[i - 1][j - v] + w);}}
一维优化
for (int i = 1; i <= n; i ++ ) // 枚举当前几个物品{int v, w;cin >> v >> w;for (int j = m; j >= v; j -- ) // 体积f[j] = max(f[j], f[j - v] + w);}必须倒序枚举体积,我们的f[j - v]用的实际是f[i - 1][j - v], 而正序枚举我们可能会先更新了
f[j - v]使得后面用到f[j - v]时实际用的是f[i][j - v]从而错误, 只要倒序就可以解决此问题

完全背包

即有n种物品,每种无限个
一般的转移方程为 f[i][j] = max(f[i - 1][j], f[i][j - v]);

优化代码

O(n*m)

for (int i = 1; i <= n; i ++ )
{int w, v;cin >> v >> w;for (int j = 0; j <= m; j ++ ){f[i][j] = f[i - 1][j];   if (j >= v) f[i][j] = max(f[i][j], f[i][j - v] + w);}
}
朴素版

O(n*m*k)

for (int i = 1; i <= n; i ++ ) {int v, w;cin >> v >> w;for (int j = 1; j <= m; j ++ )for (int k = 0; k <= j / v; k ++ )f[i][j] = max(f[i][j], f[i - 1][j - v * k] + w * k);}
方程推导

正常的思路:
f[i][j] = max(f[i - 1][j], f[i - 1][j - v] + w, f[i - 1][j - 2v] + 2w,...,f[i][j - kv] + kw)
k∈(0, j/v);
这种复杂度过高,一般接受不了

一般的方程f[i][j] = max(f[i - 1][j], f[i][j - v]);
f[i][j - v]如何得来? (注意max中的对应)
f[i][j] = max(f[i - 1][j], f[i - 1][j - v], f[i - 1][j - 2v],...,f[i][j - kv])
########f[i][j - v] = max(f[i - 1][j - v], f[i - 1][j - 2v],...,f[i][j - kv])
k是相同
可得f[i][j] = max(f[i - 1][j], f[i][j - v]);
加上价值就是f[i][j] = max(f[i - 1][j], f[i][j - v] + w);

一维优化
for (int i = 1; i <= n; i ++ ) 
{int v, w;cin >> v >> w;for (int j = v; j <= m; j ++ )f[j] = max(f[j], f[j - v] + w);
}

这里只能正序枚举,和01背包相反,因为我们转移的实际上是f[i][j - v],正序可以提前更新f[j - v]使他实际上成为f[i][j - v]符合要求倒序则为f[i - 1][j - v]不合要求

转移的注意

对于完全背包,一定要转移完全,即在从k = 0的时候也要转移进去,不能终断,否则会出现错误
如P1941 [NOIP2014 提高组] 飞扬的小鸟 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)如果直接做即完全背包的上升和01背包的下降同时转移,那么祝贺你,会得到70分的代码.

多重背包

有n种物品但是每种物品有数量限制

朴素代码
for (int i = 1; i <= n; i ++ )
{int w, v, s;cin >> v >> w >> s;for (int j = 0; j <= m; j ++ )for (int k = 0; k <= min(s, j / v); k ++ )f[i][j] = max(f[i][j], f[i - 1][j - k * v] + k * w);
}
二进制优化

把物品的个数S拆成一堆数,形成一个单独的物品,最后采用01背包去做
一般采用二进制的方法
因为一堆二进制数加起来可以加成任何数
如0001,0010,0100,1000这四个二进制数相加,可以组成任何小于等于1111的数(正数)

for (int i = 1; i <= n; i ++ ) 
{int v, w, s, k = 1;cin >> v >> w >> s;while (k <= s){V[ ++ cnt] = v * k;W[cnt] = w * k;s -= k; // 这样拆是logn个比较高效k <<= 1; }if (s > 0) // 我们减到最后可能会有剩下的,也加上{V[ ++ cnt] = v * s;W[cnt] = w * s;}
}for (int i = 1; i <= cnt; i ++ ) 
{int v = V[i], w = W[i];for (int j = m; j >= v; j -- )f[j] = max(f[j], f[j - v] + w);
}

一般够用

单调队列优化

口诀:比你小还比你强,你就出列了~~~
时间复杂度为O(nm)
因为一般m都比较大所以常用[[DP的通用优化#滚动数组]]

代码
for (int i = 1; i <= n; i ++ )
{int w, s, v;cin >> v >> w >> s;memcpy(g, f, sizeof f);for (int j = 0; j < v; j ++ ){int hh = 0, tt = -1;for (int k = j; k <= m; k += v){if (hh <= tt && q[hh] < k - s * v) hh ++ ;while (hh <= tt && g[q[tt]] - (q[tt] - j) / v * w <= g[k] - (k - j) / v * w) tt -- ;q[ ++ tt] = k; f[k] = max(f[k], g[q[hh]] + (k - q[hh]) / v * w);}}
}
思路

首先你要知道在s个物品限制下转移方程为(一般情况s >= m / v)
这里推荐看我以前的笔记

	f[i][j] = max(f[i - 1][j], f[i - 1][j - v] + w, f[i - 1][j - 2v] + 2w, ... , f[i - 1][j - sv] + sw);f[i][j - v] =          max(f[i - 1][j - v],     f[i - 1][j - 2v] + 1w, ... , f[i - 1][j - sv] + (s - 1)w, f[i - 1][j - (s + 1)v] + sw);f[i][j - 2v] = ...f[i][j - 3v] = ......r = j % v;f[i][r + v] = max(f[i - 1][r + v], f[i - 1][r] + w)f[i][r] = f[i - 1][r]经典的图啊根据完全背包优化的思路我们可以得到上面的图(也可以看我过去笔记的图片)我们能发现一个事情,因为有s的限制,在一般情况下,我们没法直接用f[i][j - v]来转移but我们可以从它的余数出发, f[i][r], 这个是值固定的,往上f[i][r + v]看图越往上我们会发现,在他们的max内的第一个都在增加v(r + v), 且当s > j / v时它就开始滑动(像f[i][j], f[i][j - v], max的数量一样,但是f[i][j]向前进了1个v, 且w的值都加了1w, 除了新进来的)这就想到了单调队列,可以靠它维护这个区间最大值,优化的点,保持队列队头最大,利用这个最大,优化计算量;并且可以计算滑动窗口内的最大值看看代码,多想想因为过于复杂还是看y的课比较好。

以前的笔记
如果你还有记忆的话应该会懂得
一般比二进制优化快

二维费用背包

很简单就是有两个费用,多开一维,记录费用即可非常简单
状态一般为 f[i][j][k]在前i个数内,在两个费用都不超过j和k的情况下的属性

可以和上面三种背包结合,相当于前缀

一般代码(二维费用01背包)
for (int i = 1; i <= n; i ++ )
{int w, v, t;cin >> v >> t >> w;for (int j = m; j >= v; j -- )for (int k = p; k >= t; k -- ){f[j][k] = max(f[j][k], f[j - v][k - t] + w);}
}

不少于问题

之前没整理到这个
简单说就是对于费用不少于的问题,这也可以和上面三种背包连用,相当于前缀

对于这个直接DP分析即可,关于不少于,直接设f[i][j]为在前i个里面,费用不少于j的情况
那么第i个可选可不选,不选就是f[i - 1][j],选的话是f[i - 1][j - v] 这时候可能会有j < v的情况,这时候也要转移,因为它符合,费用不少于j(大于了当然可以),写成这个就行f[i - 1][max(0, j - v)]。从相当于从0直接到j。

这时候回头想想,好像之前的当j < v的时候就不转移了(可看看上面代码),这是因为之前状态是费用最大为j,这就是本质的差距。

恰好问题

这是一类小问题,状态设计要改变为,恰好,转移没什么变化,但初始化有变动,一定要先设置为不可能情况,因为对于一种状态
像有许多石头,价值和重量无关,求恰好重量为j时的最大价值,石头有(前面是价值,后面是重量){1, 1},{2, 1},{2, 2}

当f[2][3]转移的时候,f[1][2]这种情况是不可能的,所以f[2][3] = max(f[1][2] + 2)
整个转移是转移不了的,但是价值不大于j时是可以的。这就是它的本质

求方案数

这类其实有点技巧,最稳妥的方法是,把题目改为恰好,这里用一个例题来说明。
求最优方案数
AcWing 11. 背包问题求方案数 - AcWing
题中要求不超过j方案最大价值的方案数,
有两种大方向

  1. 利用不超过直接求解,因此f初始化都为0,对于每种情况f[i][j]最少有1种方案,然后转移时如果可以更新就直接更替cnt,相等就加上cnt,最后直接输出cnt[n][m]即可
  2. 利用恰好,把不超过j,改为恰好为j,转移和上面一样,然后在f[n][j]中记录,最大值,并把所有的最大值相等的方案加上。
    如上代码在上面链接中
    因此对于一个问题,除了特殊记录以外,就可以直接转移或利用恰好凑出方案

而转移时如果可以更新就直接更替cnt,相等就加上cnt,这是所有方案问题的最基本的思想,不限于动态规划

关于方案的保存

问题在保存选的方案,像这个AcWing 1013. 机器分配 - AcWing比较典型,除了求解答案,还要输出每个工厂选择的机器数,可以dfs,可以像我这样原路推回去(实际上更多使用这种),
没有固定的方式,这里只说明有此类问题。

枚举的状态

正常背包的转移为f[i][j] = max(f[i][j], f[i - 1][j - v] + w)
我们依赖于第i - 1个物品进行转移,实际上还可以依赖体积,来转移。
思路为
{不选}
{选择体积最大为k的子集,并加上这个点} k <= j

对于正常的背包,这是没有用的,但对于树形等物件间有特殊关系的,有奇效.
AcWing 10. 有依赖的背包问题 - AcWing这题就需要对于每个子树分配体积,来计算
否则复杂度将爆炸

有依赖的问题

分为很多种,例如可能依赖是树形的,可能是线性的,等等。
中心思想就是转移好子集,不能言传。
AcWing 10. 有依赖的背包问题 - AcWing
487. 金明的预算方案 - AcWing题库
上面是较好的一道,也是较难的一道。
下面的较为简单适合入入门。

求具体方案

这种会问你最后选择的方案是是什么,而不是方案数,可能有限制,如选择字典序最小的最优选择方案是什么。这种有两种思路。

  1. 直接保存方案,利用string,或者vector,在转移过程中保存方案,通过比较方案来选择最小字典序,这种一般较慢,但很稳定。
  2. 先求出最优方案,再利用最优方案,从大枚举,或从小枚举,推出选择方案。这种较快,但边界和枚举设计需要考虑清楚。
    AcWing 12. 背包问题求具体方案 - AcWing
    具体代码如上

关于状态转移的奇技淫巧

  1. 有时候我们不能直接转移状态,而要合成状态,像f[i + 1][j + v] = ...这时候可能会有“卡壳”情况,即我们无法直接判断情况,这时候不妨从0出发把for (int i = 1; i <= n; i ++ )变为for (int i = 0; i < n; i ++ )这样“退位”,在某些时候有奇效如这题P1156 垃圾陷阱而这就是刷表法,[[刷表法和填表法]]

变化的价值/体积(泛化物品的背包)

对于一类题,它的价值和体积会随着你的选择顺序不同而改变,这时候需要考虑优先级,
常用方法

先设x,y为两个相邻物品,然后列出(如价值)的不等式,像如此价值的情况
先x后y w = (p + c[x]) * b[x] + (p + c[x] + c[y]) * b[y] (1)
先y后x w = (p + c[y]) * b[y] + (p + c[y] + c[x]) * b[x] (2)
我们如果要让x在前的话,那么(1) > (2)
可以解得 c[x] * b[y] > b[x] * c[y],然后我们利用这个性质排序,再进行背包处理

做了那么多题,对于背包,正确的物品排序确实很重要。

给出对应的例题试试吧P1417 烹调方案 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

在最值情况下的最值

这类题题意如求在最大价值1的情况下,最小的价值2为多少,即在最大价值1下的最小价值2,
这种算是比较好做,只要在转移出最大价值1的同时,记录最小价值2,更新时替换,相等时对比求最小。注意,前提是最大价值1,所以要对它Dp

例题P1509 找啊找啊找GF - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ulsteruni.cn/article/40805240.html

如若内容造成侵权/违法违规/事实不符,请联系编程大学网进行投诉反馈email:xxxxxxxx@qq.com,一经查实,立即删除!

相关文章

Ubuntu 22.04.4 LTS磁盘扩容

安装gpartedsudo apt updatesudo apt install gparted然后启动gpartedsudo gparted启动成功会完成一个新的对话框,直接调整磁盘大小的话会提示失败扩容查看只读文件系统的详细信息,点击Information(信息) 查看磁盘的挂载位置按顺序运行以下命令sudo -i mount -o remount -r…

一步步教你在 Windows 上构建 dotnet 系应用的 UOS 软件安装包

本文将详细指导大家如何逐步为 dotnet 系列应用创建满足 UOS 统信系统软件安装包的要求。在这里,我们所说的 dotnet 系列应用是指那些能够在 Linux 平台上构建 UI 框架的应用,包括但不限于 CPF 应用、UNO 应用、Avalonia 应用等本文将详细指导大家如何逐步为 dotnet 系列应用…

矩阵树定理 BEST 定理

傲慢的青蛙矩阵树定理 \(\text{BEST}\) 定理 证明很复杂,连 \(\text{cmd}\) 这种无敌神犇都不会,而且对定理本身的可扩展性几乎为 \(0\),即每次套用的定理都跟模板一模一样。 矩阵树 无论任何情况,一定要不能有自环 无论任何情况,一定要不能有自环 无论任何情况,一定要不…

爽了!免费的SSL,还能自动续期!

作者:小傅哥 博客:https://bugstack.cn沉淀、分享、成长,让自己和他人都能有所收获!😄大家好,我是技术UP主小傅哥。 兄弟👬🏻,当你手里有不少域名,每个域名又配置子域名,那么ssl将是一笔不小的费用。当然各个云厂商,也都有提供免费的ssl证书,但这里有一个问题,…

TimThumb——超好用的 PHP 略缩图裁剪插件

TimThumb 是一个非常简洁方便的、用于裁图的 PHP 程序。只要给它设置一些参数,它就可以生成指定图片的缩略图甚至是直接给指定的网站截图。现在很多 WordPress 主题中,都使用的是 TimThumb 这个 PHP 类库进行缩略图处理。(本博客使用的 Nana 主题中的文章略缩图也是用 TimThu…

指令优化:基于大型语言模型的指令算子的进化多目标指令优化

指令优化:基于大型语言模型的指令算子的进化多目标指令优化 摘要 基于指令的语言建模在预训练的语言模型中受到了极大的关注。 提出了一种指令优化方法,将指令生成视为一个进化的多目标优化问题,利用大型语言模型(LLM)来模拟指令运算符,包括变异和交叉。 此外,为这些运算…