算法模板:动态规划之01背包

简介: 算法模板:动态规划之01背包

前言


唤我沈七就好啦。


动态规划


核心作用:优化


当数据范围> 30 时 单纯用暴力(DFS BFS)的话指数型枚举必然超时

而动态规划能以某种比较聪明的方式来枚举所有方案。

最后按题目要求(属性)求解。


dp、递归、递推、搜索 的关系


dp 是递推,是搜索的改版,是递归的进化体.


初始化


DP 的细节包括你说的初始化问题,是没有很固定的模板的,一般情况下可以归纳以下三种情形:


求最大值,将所有值初始化为无穷小,找到 DP 的起点(边界),手动赋值;

求最小值,将所有值初始化为无穷大,找到 DP 的起点(边界),手动赋值;

求方案数,将所有值初始化为 0 ,找到 DP 的起点(边界),手动赋值,一般为 1 。

在还没有熟悉的时候,建议认真找到所有边界值,并给它们赋上初值,这样的方式肯定是正确的。

熟悉了以后,针对某些特定的题目,可以根据经验,遵循 不影响最终答案 的原则,省略部分赋值的步骤。

状态转移方程涉及到 i-1 从 i 就从1开始循环,没涉及就从 0 开始。


01背包


二维背包


有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 v[i],价值是 w[i]

求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。

输出最大价值。


dp[i] [j] 含义:前 i 个物品,背包容量 j下的最优解(最大价值):


(1)当前的状态依赖于之前的状态,可以理解为从初始状态dp[0] [0] = 0 开始决策,


有 N 件物品,则需要 N 次决 策,每一次对第 i 件物品的决策, 状态f[i] [j]不断由之前的状态更新而来。


(2)当前背包容量不够(j < v[i])

没得选,因此前 i个物品最优解即为前 i−1个物品最优解:


对应代码:f[i][j] = f[i - 1][j]。


(3)当前背包容量够,可以选,因此需要决策选与不选第 i个物品:


选: f[i][j] = f[i - 1][j - v[i]] + w[i]。
不选:f[i][j] = f[i - 1][j] 。
我们的决策是如何取到最大价值,因此以上两种情况取 max()
我们的决策是如何取到最大价值,因此以上两种情况取 max() 


为什么选的状态转移 方程 是 f[i] [j] = f[i - 1] [j - v[i]] + w[i] 呢


下面我来说一下我的理解


d p [i-1] [j-v[i]]+w[i ] 代表着 “必选第i个物品” ,怎么样才能做到“必选”呢 ?


其实只要在一开始的时候将第 i 个物品直接放入背包即可 即 +w[i]


然后只在前 i-1 个物品里选剩下的就行了。


#include<bits/stdc++.h>
using namespace std;
const int N=1e4;
int dp[N][N];// dp[i][j], j体积下前i个物品的最大价值
int v[N],w[N];
int main()
{
    int n,m;
    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 = 1 ; j <= m ; j ++)
        {
            dp[i][j]=dp[i-1][j];
            if(j>=v[i])//j<v[i] dp[i-1][j-v[i]]下标就小于0了
            dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
        }
        cout<<dp[n][m];
    return 0;
}


一维优化


因为 dp[i] 仅用到了dp[i-1]层, 那我们只需要短暂的存一下上一层,就可以去掉这层了


( 1 )dp[j]定义:N件物品,背包容量j下的最优解。


( 2 )注意枚举背包容量j必须从m逆序开始。


( 3 )为什么一维情况下枚举背包容量需要逆序?


在二维情况下,状态f【i】【j】是由上一轮i - 1的状态得来的,f[i] [j]与f[i - 1] [j]是独立的。


而优化到一维后,如果我们还是正序,则有f[较小体积]更新到f[较大体积],则有可能本应该用第i-1轮的状态却用的是第i轮的状态。


例如:由于j是从大到小循环的。假设j = 5; j >= 2; j -–;


也就是这一层(第i层)会先算出来f[5],


注意这个时候,第i层正在算f[5], 也就是说第i层没有更新f[4] f[3],也就是此时的f[4] f[3] 是i - 1层的)


那么算 f[5] 要用到比他更小的 f[4] 或者f[3]也就是i - 1层的了。


(4)简单来说,一维情况正序更新状态f[j]需要用到前面计算的状态已经被「污染」,逆序则不会有这样的问题。


#include<bits/stdc++.h>
using namespace std;
const int N=1e4;
int dp[N];
int v[N],w[N];
int main()
{
    int n,m;
    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 --)
        {
        dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
        }
        cout<<dp[m];
    return 0;
}


经典习题


小A点菜


uim由于买了一些书,口袋里只剩M元(M≤10000)

餐馆虽低端,但是菜品种类不少,有N种(N≤100),

第i种卖a元(ai≤1000) 由于是很低端的餐馆,所以每种菜只有一份。

小A奉行“不把钱吃光不罢休”,所以他点单一定刚好把uim身上所有钱花完。

他想知道有多少种点菜方法。


dp[ j ] 表示达到当前钱数时,已经求出的方案数;


每一种菜都可以选择吃和不吃


#include<bits/stdc++.h>
using namespace std;
const int N=1e4+10;
int dp[N];
int w[N];
int main()
{
  int n,m;
  cin>>n>>m;
  for (int i = 1 ; i <= n ; i ++)
  cin>>w[i];
  dp[0]=1;//因为当钱数==0时,说明当前方案已经成立,所以方案总数加 1
  for (int i = 1 ; i <= n ; i ++)
  for (int j = m ; j >= w[i] ; j --)
  {
  dp[j]=dp[j]+dp[j-w[i]]; 
        //可以选吃喝和不吃
  } 
  cout<<dp[m];
  return 0;
}


5 倍经验日


现在 A 拿出了 x个迷药,准备开始与那些人打了。

由于迷药每个只能用一次,所以 A要谨慎的使用这些药。悲剧的是,用药量没达到最少打败该人所需的属性药药量,则打这个人必输。例如他用 2 个药去打别人,别人却表明 3个药才能打过,那么相当于你输了并且这两个属性药浪费了。

现在有 n 个好友,给定失败时可获得的经验、胜利时可获得的经验,打败他至少需要的药量。

要求求出最大经验 s,输出 5 * s 。


题解部分:


dp[i]表示用i瓶药获得的最多经验。


1.如果 j 个迷药打不过,只能加 失败的经验: dp[j]=dp[j]+w1[i]


2.如果打的过 ,可以选择打败他获得获胜经验,也可以选择不打败他获得失败经验


然后就是 0 1 背包思想: dp[j]=max(dp[j]+w1[i],dp[j-v[i]]+w2[i]);


注意 如果要打败他的话,需要消耗 迷药 ,所以需要 j - v[i]


#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
long long dp[N],n,m;
long long w1[N],w2[N],v[N];
int main()
{
  cin>>n>>m;
  for(int i = 1 ; i <= n ;i ++)
  cin>>w1[i]>>w2[i]>>v[i];
  for(int i = 1 ; i <= n ; i ++)
  {
        for(int j = m ; j >= 0 ; j --)
        {
            if(v[i]>j)//如果 j 个迷药打不过,只能加 失败的经验
            dp[j]+=w1[i];
            else
            dp[j]=max(dp[j]+w1[i],dp[j-v[i]]+w2[i]);
            //如果打的过 ,可以选择打败他获得获胜经验,也可以选择不打败他获得失败经验。
            //这样问题就转化成了 0 1 背包问题 选和不选
        }
  } 
  cout<<5*dp[m];
  return 0;
 }


买干草


约翰遭受了重大的损失:蟑螂吃掉了他所有的干草,留下一群饥饿的牛.他乘着容量为C(1≤C≤50000)个单位的马车,去顿因家买一些干草. 顿因有H(1≤H≤5000)包干草,每一包都有它的体积Vi(l≤Vi≤C).约翰只能整包购买,


他最多可以运回多少体积的干草呢?


虽然问的是体积,但可以将这个问题看成价值和体积相等


这样就和背包问题 一 模 一 样 了!


#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
long long dp[N];
int v[N];
int main()
{
  int m,n;
  cin>>m>>n;
  for(int i = 1; i <= n ; i ++)
  cin>>v[i];
  for(int i = 1; i <= n ; i ++)
  for(int j = m; j >= v[i] ; j --)
  {
  dp[j]=max(dp[j],dp[j-v[i]]+v[i]);
  }
  cout<<dp[m];
  return 0;
}


完结散花


ok以上就是对 动态规划之01背包 的全部讲解啦,很感谢你能看到这儿。如果有遗漏、错误或者有更加通俗易懂的讲解,欢迎小伙伴私信我,我后期再补充完善。


参考文献


https://www.acwing.com/activity/content/19/


相关文章
|
1月前
|
算法 测试技术 C++
【动态规划算法】蓝桥杯填充问题(C/C++)
【动态规划算法】蓝桥杯填充问题(C/C++)
|
15天前
|
算法 Python
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果;贪心算法在每一步选择局部最优解,追求全局最优;动态规划通过保存子问题的解,避免重复计算,确保全局最优。这三种算法各具特色,适用于不同类型的问题,合理选择能显著提升编程效率。
32 2
|
1月前
|
算法
动态规划算法学习三:0-1背包问题
这篇文章是关于0-1背包问题的动态规划算法详解,包括问题描述、解决步骤、最优子结构性质、状态表示和递推方程、算法设计与分析、计算最优值、算法实现以及对算法缺点的思考。
69 2
动态规划算法学习三:0-1背包问题
|
1月前
|
算法
动态规划算法学习四:最大上升子序列问题(LIS:Longest Increasing Subsequence)
这篇文章介绍了动态规划算法中解决最大上升子序列问题(LIS)的方法,包括问题的描述、动态规划的步骤、状态表示、递推方程、计算最优值以及优化方法,如非动态规划的二分法。
66 0
动态规划算法学习四:最大上升子序列问题(LIS:Longest Increasing Subsequence)
|
1月前
|
算法
动态规划算法学习二:最长公共子序列
这篇文章介绍了如何使用动态规划算法解决最长公共子序列(LCS)问题,包括问题描述、最优子结构性质、状态表示、状态递归方程、计算最优值的方法,以及具体的代码实现。
129 0
动态规划算法学习二:最长公共子序列
|
1月前
|
存储 算法
动态规划算法学习一:DP的重要知识点、矩阵连乘算法
这篇文章是关于动态规划算法中矩阵连乘问题的详解,包括问题描述、最优子结构、重叠子问题、递归方法、备忘录方法和动态规划算法设计的步骤。
106 0
|
1月前
|
算法 C++
【算法解题思想】动态规划+深度优先搜索(C/C++)
【算法解题思想】动态规划+深度优先搜索(C/C++)
|
30天前
|
算法 安全 数据安全/隐私保护
基于game-based算法的动态频谱访问matlab仿真
本算法展示了在认知无线电网络中,通过游戏理论优化动态频谱访问,提高频谱利用率和物理层安全性。程序运行效果包括负载因子、传输功率、信噪比对用户效用和保密率的影响分析。软件版本:Matlab 2022a。完整代码包含详细中文注释和操作视频。
|
7天前
|
算法 数据安全/隐私保护 索引
OFDM系统PAPR算法的MATLAB仿真,对比SLM,PTS以及CAF,对比不同傅里叶变换长度
本项目展示了在MATLAB 2022a环境下,通过选择映射(SLM)与相位截断星座图(PTS)技术有效降低OFDM系统中PAPR的算法实现。包括无水印的算法运行效果预览、核心程序及详尽的中文注释,附带操作步骤视频,适合研究与教学使用。
|
15天前
|
算法 数据挖掘 数据安全/隐私保护
基于FCM模糊聚类算法的图像分割matlab仿真
本项目展示了基于模糊C均值(FCM)算法的图像分割技术。算法运行效果良好,无水印。使用MATLAB 2022a开发,提供完整代码及中文注释,附带操作步骤视频。FCM算法通过隶属度矩阵和聚类中心矩阵实现图像分割,适用于灰度和彩色图像,广泛应用于医学影像、遥感图像等领域。
下一篇
无影云桌面