背包问题汇总1(01背包,完全背包)

#背包问题

*背包问题是动态规划算法中最经典的问题,掌握好背包问题基本上动态规划也算入门了,本篇博客对动态规划算法的理论知识不予阐述,我们通过例子去掌握应用,反过来再去看概念想必无师自通了。~~~~*

##01背包问题

题目描述

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

第 i 件物品的体积是 vi,价值是 wi。

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

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8

算法思路:重点!!!!!

题目太长不想看??,我来简单说一下,就是说有n个物品,每个物品对应一个体积和价值,请问,如何将这些物品放到一个体积有限的背包中去,且每个物品只能用依此!!!!!,我们一步一步去思考这个问题。

下文中“…”标识的意思是从什么到什么。

正常来说,我们一般会优先选择单位价值较大的物品放进去,但我们看这个样例,如果按照这种思路去放(1,2),(2,4)(即第一个物品和第二个物品)单位价值最大,我们把它放进去,此时体积被占用了3还剩2体积可用,但剩余物品无法放置,我们来看看这是不是最优解,em。。。,显然将第二个和第三个物品放进去价值最大,所以这种思路是有问题的,这个思路是贪心的思路。

那我们怎么放???我们换一种思路,对于每种物品来说,我们有两种选择,就是把他放进背包或者不放进背包,对吧,不准弄坏物品hhhh。有了这个思路后,我们就可以采用一个一个物品去判断,但怎么去判断??这里的思路需要大家去记住,就是对于每一个物品的选择,我们把所有体积都枚举一遍,这是个什么鬼,听的迷迷糊糊的。举个具体的例子,我们上来就看第一个物品(体积1,价值2),枚举体积顾名思义,就是把背包从0到最大的体积每个都考虑一遍,即0…15(样例),我们来看当体积为0的时候,我们来选这个物品,能选吗?肯定不能,对吧。当体积为1的时候能选吗?可以,能不选吗?也可以(滑稽~~~),奇怪,我到底选还是不选,这里我们引入一个最优解的思路,我们来想,对于第一个物品,且背包体积为1的时候最优解是什么,换句话说就是对于这种状态的背包我们怎么放物品才能使价值最大,显然我们放进去价值最大,之后的体积呢2…15是不是也是一样。这里注意,我们只考虑第一个物品,别去看剩下的。好,第一个物品我们判断完了,该第二个物品了,继续体积从0…15依此看,体积为0,不用说,想放也放不进去;体积为1,也放不进去;体积为2,咦,我好像能放了,那我是放还是不放呢,来吧,最优解思路又来了,放进去后价值为4,显然我们放进去,我们继续看,当背包体积为3的时候,放进去,不解释了,此时价值为6,然后继续遍历背包体积到15即可,看第三个物品,判断方法和上面相同,但我们来看某个特殊的情况,当体积为3的时候,我们可以选择放,也可选择不放,但我们对比之后发现原本背包体积为3时,最大价值为6,而如果把这个物品放进去最大价值就变成了4,显然不符合最优解的要求,那么就不放。

好了 上面说了一大堆废话,我们具体来看一下,我们怎么去实现上面的理论,在这里,我们需要去将每个状态去保存下来,什么是状态???我们看,对于第几个物品都有某个体积且在该种情况下有一个对应的最大价值,举个例子,对于第二个物品,当体积为4的时候所放物品最大价值为6,这就是一种状态。我们怎么去保存呢???,技巧来了,对于动态规划问题来说,每一种状态就可以用数组的一个维度去表示,比如,这里有两个状态(第几个物品,当前背包体积是多少),这个时候我们就用一个二维数组dp[n][n]去表示对于第i个物品当体积为j时最大价值是多少(即数组的值),有几种状态就有几维i,不要过多纠结他在内存中的状态,三维,四维都是有的。每一维就代表一个状态。那么如何判断选还是不选呢,很简单,就是把某种状态下的物品选还是不选的最大价值求出来去max就可以了

不选 dp[i][j]=dp[i-1][j]
选 dp[i][j]=max(dp[i-1][j],dp[i-1][j-tiji[i]]+jiazhi[i]);

初学者不太理解这个,正常,我刚上来也不理解,我们来简单看一下,我们每放第几个物品的时候都是从上一个物品的状态推出来的,这点注意~~~~,举个例子,我们推第三个物品的时候,从什么推,我怎么判断选还是不选能满足最优解,当然通过上一层去判断,只有将第二个物品对应的状态判断完所有最优解,才能判断下一个,这点不理解一定要好好体会。如果第三个物品能选,就要判断不选的时候价值,和选了价值大小取max,dp[i-1][j-tiji[i]]+jiazhi[i],i-1(上一个物品),j-tiji[j]要放进去物品就要减去当前物品体积(应该很好理解)+当前物品价值。

代码附上

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
int w[MAXN];    // 重量 
int v[MAXN];    // 价值 
int f[MAXN][MAXN];  // f[i][j], j重量下前i个物品的最大价值 

int main() 
{
    int n, m;   
    cin >> n >> m;
    for(int i = 1; i <= n; ++i) 
        cin >> w[i] >> v[i];

    for(int i = 1; i <= n; ++i) 
        for(int j = 1; j<=m; ++j)
        {
            //  当前重量装不进,价值等于前i-1个物品
            if(j<w[i]) 
                f[i][j] = f[i-1][j];
            // 能装,需判断 
            else    
                f[i][j] = max(f[i-1][j], f[i-1][j-w[i]]+v[i]);
        }
		
	
	cout<<f[n][m]<<endl;           

    return 0;
}

接下来我们来看如何优化的问题,我们一般优化采用两种思路,要么从时间复杂度上优化,要么从空间复杂度上优化,由于背包问题是一种NP Hard问题,时间复杂度是无法进行优化的,但我们可以对空间进行优化,注意我上面说过,对于第i个物品选或者不选都是从上一层推出的即i-1,这里我们就可以进行约去(理解不了没关系,记住),原因是因为,i只和i-1有关系,就可以把这一层约去,想想看,反正每一次都是曾上一层状态推出来的,那么我当前数组保存的一定是上一层的状态,对吧。但我们需要注意一点,我们需要把内存循环倒过来,为什么,我举个例子(假设内层循环正着来),dp[2]=4,dp[4]=dp[2]+2,我们看这个例子,我们希望dp[4],是保存上一层的状态,即第i-1物品对应体积为4的最大价值,但是dp[4]是由dp[2]更新的,但dp[2]已经被提前更新了,这是显然dp[2]保存的是当前物品的状态(即第i个物品,而不是第i-1个物品,),理解不了反复看,这里很重要,一定要搞明白。但是我们倒着循环就没问题了,不信你试试,你想dp[2]一定比dp[4]更新的晚,因为体积是正数嘛,j-w[i],一定比当前体积要小,所以说,如果内存循环倒着来就可以保证数组为更新的最优解是从上一层推出来的。

代码走起来

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

完全背包

掌握01背包后完全背包就很好理解了,完全背包的意思就是,对于每个物品可以无限选,其他条件不变。
很简单,我们把01背包内存循环再倒过来,也就是又变成正着来,是不是变来变去有点绕hhhh。dp[2]=4,dp[4]=dp[2]+2还是这个例子,如果正着来dp[2]就是第i个物品的状态,但我每个物品可以无限选,当然状态可以是现在的物品了。

好了直接来例题了

有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。

第 i 种物品的体积是 vi,价值是 wi。

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

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 种物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
10

代码:

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

本博客特别鸣谢acwing题库,~~,如果想学算法的同学可以去这个网站上https://www.acwing.com/
希望大家都能成为算法大佬hhhh。
如有问题欢迎指正,每天更新算法,可以私信我哦,嘻嘻嘻嘻嘻

创作不易,如果对你有帮助请留下小赞赞哦,谢谢>…<

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

就是氧气c

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值