动态规划——背包问题(详解)

动态规划——背包问题(详解)动态规划是我最早接触的算法,一开始非常简单,固定模板题,后来愈发愈发难起来了,条件,状态压缩等等,难点主要是,状态怎么表示,状态转移方程怎么写,这篇文章将会从背包五大问题详解,希望能帮助到大家去类比,思考其他动态规划题目。首先先来看看动态规划的定义:动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

动态规划是我最早接触的算法,一开始非常简单,固定模板题,后来愈发愈发难起来了,条件,状态压缩等等,难点主要是,状态怎么表示,状态转移方程怎么写,这篇文章将会从背包五大问题详解,希望能帮助到大家去类比,思考其他动态规划题目。


首先先来看看动态规划的定义
动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。

看完定义我们开始应用了!来看基本的01背包问题,也是最经典的动态规划01背包问题(点此跳转题目,下同)
[ps:建议而外开个网页去打开题目链接,用Ctrl+tab食用更佳]

这题所求的是V容积下能够装的物品的最大值。很显然有容积的限制有时候你不能全部都装,又有体积和价值的不确定性,可能你选小体积的反而会有更大的价值,如果去暴力枚举时间复杂度呈指数显然不能ac,那么就需要六大算法之一动态规划了。

明确的一点此题是求最大值,而每个物品至多选一次,也就是只有选和不选俩个状态。我们可以一个一个物品去决策这个物品是该选还是不该选,这只需要一个max函数就能完成了。现在的问题是如何去定义,怎么去记录这个决策过程。依靠现有的知识能储存多组数据的只有数组了,我们可以先定义一个二维数组dp[N][N]去记录这个过程。

int dp[N][N]; //dp[i][j]表示容积为j的背包只装前i个物品可以达到的最大价值

这也是动态规划的第一步,明确数组的含义或者集合的含义。

接下来可以一个一个去决策了,先预知一下,肯定需要一个for循环去枚举物品,枚举完物品又需要一个for循环去枚举体积。在选或不选的情况下去择优,更新dp[i][j],n2的时间复杂度,显然对付这题很容易了。

for(int i = 1; i <= n; i++) {
    for(int j = 0; j <= m; j++) {
        dp[i][j] = dp[i-1][j]; //一开始是都选择不装
        if(j>=v[i]) dp[i][j] = max(dp[i][j], dp[i-1][j-v[i]]+w[i]);  
        //只有物品体积大于背包容积了才可以开始决策择优选或不选。
    }
}

那么最终答案就是dp[n][m],在只考虑前n个物品最大容积为m的背包可以装的物品的最大价值ac代码:

#include <iostream>

using namespace std;

const int N = 1010;

int n, m;
int v[N], w[N];
int dp[N][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 = 0; j <= m; j++) {
            dp[i][j] = dp[i-1][j];
            if(j>=v[i]) dp[i][j] = max(dp[i][j], dp[i-1][j-v[i]]+w[i]);
        }
    }

    cout << dp[n][m] << endl;
    return 0;    
}

可能有人了解到可以空间压缩,滚动数组等等,在此先不讲,我们前面先讲思维。(有兴趣的我后面可能有浅讲一手,你们可以浅看一手)
好回归正题,我们来总结一下01背包问题。
首先是一个状态表示二维数组dp[][],其次就是状态计算,怎么得到这个dp[i][j],其实也挺简单,刚入门就是这么简单。

(建议消化一下,新知识的接纳需要沉淀)

好我们开始下一个 完全背包问题

与01背包不同 的 是,此问题的物品可以选择无限个;
状态表示跟01背包如出一辙,你问为什么,这么类似的题你不用类似的状态表示么,你还能想出其他的么,想出了当我没说

而状态计算跟刚刚也一样么,不错,一样的地方是一模一样的,只需要再添一重循环去循化个数就好了。这也是动态规划题目经常会遇到的设定,会给出体积限制,数量限制等等限制。代码如下
 

#include<iostream>

using namespace std;

const int N = 1010;

int dp[N][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 = 0 ; j<=m ;j++) {
            for(int k = 0 ; k*v[i]<=j ; k++)
                dp[i][j] = max(dp[i][j],dp[i-1][j-k*v[i]]+k*w[i]);
        }
    }

    cout<<dp[n][m]<<endl;
}

欸,复制过去为什么没ac,哈哈显然对于这道题n3的时间复杂度就tle了,这里先不说ac代码了,如果你仔细看完我对01背包的描述,这上面的代码应该是一边过,模拟一下代码就好了,很清晰。

接下来是 多重背包问题

与上述俩个问题不同的是既不是只能选一个也不是无限选,多重背包问题中是每个物品有数量限制,只能选几个。
这就是数量限制了,加嘛,加条件而已,谁都会。

#include <iostream>

using namespace std;

int dp[105][105];
int v[105],w[105],s[105];

int main(){
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d%d%d",&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++){
                if(j>=k*v[i]){
                    dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i]]+k*w[i]);
                }
            }
        }
    }
    
    printf("%d",dp[n][m]);
    return 0;
}

欸,为什么这题n3方的时间复杂度既然过了,因为这题数值范围比较小的说(不然你能过??)

三题下来貌似很简单嘛,就很模板化了,接下来继续看 分组背包问题

此题与上述三题又有点不一样了。他将所有物品分成几个组,欸每组只能选几个。他不关心你每个能取几个了,他关心你每组能取几个了。emm稍有变通一下,我们刚刚关心每个第一重循环就去枚举物品个数,那我们现在关系每组,就只需要第一组去枚举组数就好了。

#include <iostream>
using namespace std;

const int N=110;
int dp[N][N];  //只从前i组物品中选,当前体积小于等于j的最大值
int v[N][N],w[N][N],s[N];   //v为体积,w为价值,s代表第i组物品的个数
int n,m,k;

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=0;j<=m;j++){
            dp[i][j]=dp[i-1][j];  //不选
            for(int k=0;k<s[i];k++){
                if(j>=v[i][k])  dp[i][j]=max(dp[i][j],dp[i-1][j-v[i][k]]+w[i][k]);  
            }
        }
    }
    cout<<dp[n][m]<<endl;
}

最后最后就是混合背包问题了,顾名思义,将上述背包混合起来,有的关心个数,有的关心组数,有的有条件限制,有的有数量限制,请听题 混合背包问题

如果你会了这题,那么证明你不是小白了,因为二维很难做,在此先不贴ac代码了。
好我们认识了这么多的背包问题,也算是动态规划入门了,现在你应该也对动态规划有所了解了,总比贪心好做吧!我们来归纳一下基础的动态规划题。


根据闫氏DP分析法,总的包括俩部分
一部分是状态表示:也就是你需要去找一个集合去不重不漏的把所有情况涵盖在内,之后是解释这个集合的属性,可能是最大值,最小值,也可能是数量,直观感受就是去描述你这个数组代表着什么;
另一部分就是去状态计算:去写状态转移方程,直观的就是去想你当前这个单元是有哪几部分构成,如何去决策择优。例如01背包问题,dp[i][j]里面由dp[i-1][j]和dp[i-1][j-v[i]]+w[i]俩部分构成。咳咳肯定有小伙伴懵逼,我来翻译一下。就是说在容积为j的背包只挑前i个物品的最大价值可能是在容积为j的背包只挑前i-1个物品的最大价值,也可能是在容积为j-v[i]的背包只挑前i-1个物品的最大价值(第i个已经挑了)有没有顿悟了(没有在返回几行再看一遍/头槌)。至此动态规划–背包问题入门思维版已经结束了,相信你对背包问题已经了解了,慢慢感受状态表示和状态计算的魅力吧。动态规划——背包问题(详解)

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/164412.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • tomcat运行solr

    tomcat运行solr

    2021年6月16日
    80
  • VUE学习笔记——es6对象合并 数组转对象

    VUE学习笔记——es6对象合并 数组转对象constarr=[{date:”2018-11-18″,name:”demo1″},{date:”2018-11-19″,name:”demo2″}];consttarget={};arr.forEach(a=>{constsource=JSON.parse(`{“${a.date}”:”${a.na…

    2022年9月4日
    3
  • Python要如何实现(列表)排序?

    Python要如何实现(列表)排序?排序,是许多编程语言中经常出现的问题。同样的,在Python中,如何是实现排序呢?(以下排序都是基于列表来实现)一、使用Python内置函数进行排序Python中拥有内置函数实现排序,可以直接调用

    2022年7月5日
    21
  • 如何保证docker2375端口的安全

    如何保证docker2375端口的安全情景再现:之前有很多朋友提过,当使用docker-maven-plugin打包SpringBoot应用的Docker镜像时,服务器需要开放2375端口。由于开放了端口没有做任何安全保护,会引起安全漏洞,被人入侵、挖矿、CPU飙升这些情况都有发生,今天我们来聊聊如何解决这个问题。问题产生的原因首先我们要明白问题产生的原因,才能更好地解决问题!Docker为了实现集群管理,提供了远程管理的端口。DockerDaemon作为守护进程运行在后台,可以执行发送到管理端口上的Docker命令。当我们修改do

    2022年6月13日
    217
  • iframe标签(页面嵌套)

    开发工具与关键技术:VS<iframe>作者:听民谣的老猫撰写时间:2019/6/1018:15上面两张图是两个不同的页面但是它们的基本框架都是一样,每点击一次左边的导航栏改变的都是中间的内容区域。也就是说共同的框架都是没有改变的,改变的是中间的内容。有没有什么方法可以不改变外面的基本框架只改变中间的内容???我们可以用页面嵌套方法来达到这一要求。页面嵌…

    2022年4月8日
    79
  • 共享打印机无法连接打印,错误代码0x0000011b_打印机共享错误0x000001

    共享打印机无法连接打印,错误代码0x0000011b_打印机共享错误0x000001WIndows无法连接共享打印机,错误码:0x0000011bWin10电脑1直连的打印机,设备了共享。从另一个电脑2访问电脑1的共享打印机,连接提示错误0x0000011b,如下:经询问使用人,之前电脑2是可以正常连接到电脑1的共享打印机的,只是最近几天突然连接失败了。后得知电脑1最近有更新过系统补丁。经排查,通过卸载KB5005565补丁,重启电脑1后,电脑2成功连接到共享打印机,测试打印正常。处理过程:1.打开控制面板-程序-程序和功能-已安装更新。找到对应的KB5005565补丁,右

    2022年9月10日
    1

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号