背包问题就是陪你看花开向阳

2024-05-03 11:58
文章标签 问题 背包 向阳 看花开

本文主要是介绍背包问题就是陪你看花开向阳,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

转载原文是http://www.cnblogs.com/forever97/p/bagproblem.html

 这个是专门讲01背包问题

http://blog.csdn.net/mu399/article/details/7722810

自己看懂了的


一开始接触背包问题,总会有些困扰,无法完全理解,或者说不断地忘记所谓的公式,特神说分享,可以让许多人少走弯路,我觉得颇有道理,其实我想做的,只是努力让让算法变得看上去简单易懂一些,因为我也曾是个迷惘无助,没有方向的初学者。

  才疏学浅,言语颇显幼稚,如有纰漏请指教。

  允许我摆脱背包单调的题面,给题目一些我喜欢的模样:

 

01背包问题

  浙工大的向日葵地现在需要种植一些新的向日葵,现在有一些向日葵,你知道每株向日葵需要占用的位置大小,以及它们能产生的美丽值,告诉你最多能占用的位置。

  如果没有接触过背包问题,可能有些人会有性价比的思想,就像淘宝购物时都会货比三家。

  这种思想很单纯,也很符合常理。将美丽度除以占用位置大小,按照比值排序,这样子,每次就可以选到性价比最高的向日葵,这种想法在ACM中也有一席之地,我们称之为贪心算法。

  但是显然,有些情况下,这是不成立的,为什么呢?因为空缺位置的浪费。如果有刚好可以填满位置的高性价比向日葵那么当然这是最优解,但是如果按照比值排序选取一些最优向日葵后,剩下的空位,放不下别的向日葵,但是别的比值较低的向日葵组合选取,可以获得更多的美丽值,当然后者的做法可以让这片向日葵田更加地美丽。就像,“买买买”也不是什么好事情,做出正确的选择,更为重要。

  那么如何去正确选择需要种植的向日葵呢?答案是动态规划。动态规划的特点在于考虑每个阶段的最优情况。每个状态是从上一个状态中获取,而不是凭借单个的最优情况自已为是地作出错误判断。

  现在给出状态方程,记dp[i][j]给出空位置大小为j,在对第i株向日葵做出决策时的最大美丽值,w[i]为第i株向日葵的位置占用,v[i]为第i株向日葵所能够产生的美丽值。

  能够得到动态规划方程,dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]),就是每一次,对于不同的已有情况,选取新的向日葵是更优的,那么就保留这个更优值,否则,保留原来的值,那么对于每个dp[i-1][j],在对第i株向日葵判断时,都是最优的,那么所得到的dp[i][j]也均是最优的,最后得到的dp[i][j],也必然是最优的,最后获得的dp[i][j]中的最大值,当然就是最大的美丽值了。

  那么,这个方程一定要占用那么大的空间么?答案是,不需要。

  我们从这个方程可以得到,每次dp[i][j]都是从dp[i-1][j]中推导出来的,那么之前所记录的dp[0..i-2][j]在当前的步骤都是没有作用了的,那么,所占用的空间便可以节省了,所以最后得到的核心代码是

?
1
2
3
for ( int i=1;i<=n;i++)
     for ( int j=V;j>=w[i];j--)
         dp[j]=max(dp[j],dp[j-w[i]]+v[i]);

  最后dp[0..V]中的最大值就是答案,那么为什么j的循环是倒序的呢,我想一定有人有这样的困惑,那是因为每株向日葵是独一无二的,在一种情况中只可以被选取一次,如果正序的话,因为每个dp[j]是从dp[0..j-1]中推导出来的,那么在从前往后推导的过程中,会出现反复选取同一株向日葵的情况了,当然,这也是一种题型,在后文中会提到。

  那么为什么答案不是dp[V],而是dp[0..V]呢?因为,位置占用刚好的时候未必是最大值,或者更糟糕的时候,无法占用全部的位置。但是如果说,我们要用向日葵恰好填满所有空缺,dp[V]就是最终答案了,差之毫厘,就是完全不同的情况。

  当然在这段核心代码和下文的代码中需要注意dp[i][j]的初始值都为0。

  以上就是所谓的01背包的模型和其优化,当然,美丽值这种东西我随便写写,美丽是不可以被丈量的,艺术也一样,一个人的努力更是。这个社会的浮夸所导致的结果主义,我们未必需要跟随。

完全背包问题

  现在,让我们来换一种情况,向日葵不是单株给出所占位置大小和美丽值,而是现在有许多不同种类的向日葵,我们来选取一些,使得美丽值最大。

  让我来再明确一下这种情况和刚才那种情况的差别,就是每种给出的向日葵是可以无限使用的,要是你喜欢这种样子的向日葵,你不用担心它只有一株,你可以一直去用这种向日葵来装点这个校园,那么也许你猜到了动态规划的核心代码,没错,就是第一个给出的核心代码把循环改为正序后产生的代码

?
1
2
3
for ( int i=1;i<=n;i++)
     for ( int j=w[i];j<=V;j++)
         dp[j]=max(dp[j],dp[j-w[i]]+v[i]);

   这种情况,就是完全背包类型的题目了。

  是不是对背包系列的问题有了那么一些感觉呢?

多重背包问题

  那么趁热打铁,再来看一种新的情况,我们对第二种情况加一个限制条件:每种种类的向日葵只能选取有限的个数,这个时候应该怎么做呢?

  用c[i]表示第i种向日葵最多可以选取的次数。

  其实很简单,在01背包的核心代码加入一重循环,即增加选取该向日葵的株数c[i]的循环,就可以完成这种类型的正确选取:

?
1
2
3
4
for ( int i=1;i<=n;i++)
     for ( int j=V;j>=w[i];j--)
         for ( int k=1;k<=c[i];k++)
             if (k*w[i]<=j)dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i]);

  那么一定要每种情况都枚举么?一种类型的向日葵选取1株,2株,3株都尝试过去是否过于麻烦?这个时候二进制拆分的思想就可以起优化作用了,我们可以将向日葵的可选株数,进行拆分,比如7=4+2+1,那么在完全背包的模式下,只要枚举4,2,1三种情况,就可以了,因为在枚举选取两株是否可行的时候,会调用到之前的1株情况,那么就可以产生选取1,2,3株的不同情况下的最优值,当4被枚举到的时候,1至7株的情况就全部被枚举过去了,这个优化在执行的时候,效果还是很显著的,代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
for ( int i=1;i<=n;i++){
     for ( int k=1;k<=c[i];k<<=1){
         for ( int j=V;j>=k*w[i];j--)
             dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i]);
         c[i]-=k;
     }
     if (c[i]){
         for ( int j=V;j>=c[i]*w[i];j--)
             dp[j]=max(dp[j],dp[j-c[i]*w[i]]+c[i]*v[i]);
     }
}

  这种背包模型就是多重背包了,二进制拆分处理的时候注意最后剩余的部分不要忘记。

二维费用背包问题

  接下来,再看一下更为复杂的问题,如果,向日葵不仅需要占用有限的位置,还需要占据土地中有限的水资源,这个时候应该如何选取向日葵使得美丽值最大呢?也许理解了上面几种情况,你可以很容易地想出方法,增加一重循环,同时dp时增加一维,这样就可以完成这个任务了。代码如下:

?
1
2
3
4
for ( int i=1;i<=n;i++)
     for ( int j=w[i];j<=V;j++)
         for ( int k=u[i];k<=W;k++)
             dp[j][k]=max(dp[j][k],dp[j-w[i]][k-u[i]]+v[i]);

  上面的代码明显是二维费用完全背包的核心代码,至于01背包和多重背包我想应该可以轻易类推出来。

分组背包问题

  现在,增加一些有意思的元素,采购向日葵的时候,店家说,我这些向日葵都有一个分组,你所选取的向日葵都分别从属于不同的组,这样子搭配才美丽,也就是说,现在,在每个组中,我们至多只能选取一株向日葵,然后在这个前提下,使得向日葵的美丽值最大。

  先来看一下核心代码:

?
1
2
3
4
for 所有的组k 
     for j=V..0
         for 所有的i属于组k
             dp[j]=max(dp[j],dp[j-w[i]]+v[i]);

  在这个问题中,最为重要的就是如何处理在一个组中只选取一株向日葵的问题,我们对比一下01背包的核心代码和现在的核心代码,微妙的差别就在于循环的前后位置,01背包是在枚举每株向日葵的时候,枚举不同的位置分配,而现在是在枚举不同的位置分配的时候,枚举每个组的向日葵。因为先枚举分配的位置大小可以防止在同一个位置分配的最优情况中,同一组的向日葵有多个被选中。现在这个循环的顺序可以避免这种情况的出现。上述模型就是所谓的分组背包问题了。

树形背包问题

   接下来来看另一种情况,如果店家说,有些向日葵必须要在种植了一些向日葵后种植才会好看,不然就显得很糟糕,现在告诉你他们的依赖关系,请你选取一些向日葵使得他们的美丽值最大,这种情形就比较麻烦了,就像技能树一样,要开启上面的技能,才能够继续开启下面的技能,这种问题的解决方法,就是树形DP,首先根据依赖情况构建一棵树,你可能会说,那不是森林么?因为有些物品没有依赖关系呀。这个时候我们就建立一个虚根节点,将所有的树根作为它的儿子节点。

  做法是,在DFS的同时,进入每个儿子节点计算在不同分配位置大小的情况下获得的最大美丽值,最后上传最优值。注意在进入每个儿子节点的时候,该节点是必须选取的。可能这么说还是让人很迷糊,那么还是看一下搜索的核心代码再结合理解一下,会比较好:

?
1
2
3
4
5
6
7
8
9
void dfs( int x){
     vis[x]=1;
     for ( int i=w[x];i<=m;i++)dp[x][i]=v[x];
     for ( int i=1;i<=n;i++) if (vis[i]==0&&map[x][i]){
         dfs(i); for ( int j=m;j>=w[x];j--)
         for ( int k=1;k<=j-w[x];k++)
         dp[x][j]=max(dp[x][j],dp[x][j-k]+dp[i][k]);
     }
}

  在代码中,我用map这个数组记录了x和i节点是否是父子关系,同时标记节点是否被访问,减少运算量。那么搜索一遍根节点就可以获得这个问题的最优值了。这种问题,我们称为树形背包问题。

 

  背包问题的种类并不是我不断改变选取向日葵的方式就可以枚举得完的,关键是如何在掌握了基础的几种背包问题之后,在一个复杂的问题中能够快速找到模型,针对问题想出解决方案,这是一个ACMer所需要拥有的素质。

  前段时间,一些事情闹的很不愉快,很多人把ACM说的一无是处,其实,我并不反对算法无用论,但是,ACM所给我带来并不仅仅是算法和数据结构而已,而是,在ACM这个圈子里,我很开心可以认识许多积极向上,对生活充满信心,即使被难题所困扰,仍然不会放弃希望的人,那些,并不能被所有人看到的东西,弥足珍贵。我深为自己身体里所流淌着的一些激昂而温和的情感,感到骄傲。

  好像,开始一些伤感的话题了,我们回归正题,来看几道ACM的题目,来更好地理解一下背包问题的运用:

HDU - 5410 CRB and His Birthday 

  题目大意:CRB生日,妈妈要给他买礼物,妈妈有M元钱,这家店有N种礼物,因为店长和妈妈是熟人,所以若第i种礼物买x件的话,店长会给妈妈Ai*x+Bi颗糖果,现给出每种礼物的单价、Ai值与Bi值,问妈妈最多能拿到多少颗糖果。

  题解:这是刚在暑假多校联赛时做过的题目,很显然这是一种背包问题,关键在于处理第一个特殊的礼物,这就相当于是一种特惠,跟现在的淘点点每日首单打折的差不多,b[i]的贡献仅在于买该种礼物的第一件,所以引入一维参数,表示是否买过这个物品,然后做完全背包即可。代码见下方:

POJ - 1742 Coins

  题目大意: 有n种硬币,这n种硬币的价值为a[i],第i种硬币的个数为c[i]个,问有多少种方案,使这些硬币加起来等于m。

  题解:这种题型可能有人说,叫背包方案数,但是其实就相当于是一个布尔型的多重背包问题,但是对于布尔型的多重背包问题,除了二进制拆分之外,有一种更为有效的方法,在每次更新方案时,记录某个硬币使用的次数,当次数大于给出的个数时就不进行方案更新。除此之外就是普通的多重背包了,代码见下:

HDU - 5445 Food Problem

  题目大意:有n种食物,m种车,每种食物都有提供的能量值,占的空间,和数量,每种车有价值,可用空间,和数量,求获得一定能量最少需要花费多少代价。

  题解:这是今年长春赛区网络赛的题目,乍一看,似乎是颇为复杂的题目,主要在于车和食物两边都有限制,没有一个固定的可供代价可以分配,所以显然一遍背包问题是不够的,那么做两遍背包问题呢?首先用背包计算出要获取一定数目的能量,最少需要占用多少空间,然后计算出获得题目所需能量,最少需要多少空间,同理,接下来,第二次使用背包计算出使用不同数目的租金,最大可以获得多少空间,那么大于空间的最小租金就是答案,同时多重背包可以用二进制拆分优化。

HDU - 3339 In Action

  题目大意: 有n个电站,每个电站都有一定的电量,电站之间有一定距离,我们要从0点出发去占领一些电站,使得占领的电站电量之和超过总电量的一半,求达到条件所要走的最短距离。如果可能的话,输出距离,否则输出不可能。

  题解:这道题涉及到了背包问题和其它算法的组合,个人感觉非常巧妙,首先计算所有点到起点的最短路,就得到了代价,然后又有价值,那么显然就是一个01背包问题,于是得解。

  暂且枚举这么几题我觉得比较典型的题目,加深一下对背包系列问题的理解。

   背包问题的解法在许多动态规划的题目中都有一定的参考意义,熟练掌握,能举一反三,对其它类型的动态规划也有一定的帮助,所以,认真学习一下背包问题还是很有必要的。

  向日葵的季节已经过去,ACM的路还长。

这篇关于背包问题就是陪你看花开向阳的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/956696

相关文章

关于Nginx跨域问题及解决方案(CORS)

《关于Nginx跨域问题及解决方案(CORS)》文章主要介绍了跨域资源共享(CORS)机制及其在现代Web开发中的重要性,通过Nginx,可以简单地解决跨域问题,适合新手学习和应用,文章详细讲解了CO... 目录一、概述二、什么是 CORS?三、常见的跨域场景四、Nginx 如何解决 CORS 问题?五、基

MySQL安装时initializing database失败的问题解决

《MySQL安装时initializingdatabase失败的问题解决》本文主要介绍了MySQL安装时initializingdatabase失败的问题解决,文中通过图文介绍的非常详细,对大家的学... 目录问题页面:解决方法:问题页面:解决方法:1.勾选红框中的选项:2.将下图红框中全部改为英

Nginx启动失败:端口80被占用问题的解决方案

《Nginx启动失败:端口80被占用问题的解决方案》在Linux服务器上部署Nginx时,可能会遇到Nginx启动失败的情况,尤其是错误提示bind()to0.0.0.0:80failed,这种问题通... 目录引言问题描述问题分析解决方案1. 检查占用端口 80 的进程使用 netstat 命令使用 ss

mybatis和mybatis-plus设置值为null不起作用问题及解决

《mybatis和mybatis-plus设置值为null不起作用问题及解决》Mybatis-Plus的FieldStrategy主要用于控制新增、更新和查询时对空值的处理策略,通过配置不同的策略类型... 目录MyBATis-plusFieldStrategy作用FieldStrategy类型每种策略的作

linux下多个硬盘划分到同一挂载点问题

《linux下多个硬盘划分到同一挂载点问题》在Linux系统中,将多个硬盘划分到同一挂载点需要通过逻辑卷管理(LVM)来实现,首先,需要将物理存储设备(如硬盘分区)创建为物理卷,然后,将这些物理卷组成... 目录linux下多个硬盘划分到同一挂载点需要明确的几个概念硬盘插上默认的是非lvm总结Linux下多

Python Jupyter Notebook导包报错问题及解决

《PythonJupyterNotebook导包报错问题及解决》在conda环境中安装包后,JupyterNotebook导入时出现ImportError,可能是由于包版本不对应或版本太高,解决方... 目录问题解决方法重新安装Jupyter NoteBook 更改Kernel总结问题在conda上安装了

pip install jupyterlab失败的原因问题及探索

《pipinstalljupyterlab失败的原因问题及探索》在学习Yolo模型时,尝试安装JupyterLab但遇到错误,错误提示缺少Rust和Cargo编译环境,因为pywinpty包需要它... 目录背景问题解决方案总结背景最近在学习Yolo模型,然后其中要下载jupyter(有点LSVmu像一个

解决jupyterLab打开后出现Config option `template_path`not recognized by `ExporterCollapsibleHeadings`问题

《解决jupyterLab打开后出现Configoption`template_path`notrecognizedby`ExporterCollapsibleHeadings`问题》在Ju... 目录jupyterLab打开后出现“templandroidate_path”相关问题这是 tensorflo

如何解决Pycharm编辑内容时有光标的问题

《如何解决Pycharm编辑内容时有光标的问题》文章介绍了如何在PyCharm中配置VimEmulator插件,包括检查插件是否已安装、下载插件以及安装IdeaVim插件的步骤... 目录Pycharm编辑内容时有光标1.如果Vim Emulator前面有对勾2.www.chinasem.cn如果tools工

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动