0-1背包问题

本文介绍了0-1背包问题,这是一个经典的动态规划问题。作者通过自己的学习经历,详细解释了如何用动态规划解决这个问题,包括状态表示、状态转移方程以及如何优化代码,同时强调了动态规划在减少重复计算和降低时间复杂度上的优势。
摘要由CSDN通过智能技术生成


前言:

本人三本菜鸡,在大一的时候,室友选c++期末大作业选的正好是这个,当时也在网上找了一下这个算法的相关代码,但是看不懂。后来自学算法的时候学到了动态规划,正好遇到了这个题,可是时过半年,还是没有明白这个动态规划,或者是说明白了思想,但是不会写代码。后来因为要准备蓝桥杯了(因为学校没有ACM队),所以还是强迫自己弄明白这个,看了很多视频,绕了很多弯路,最后终于明白了这题。因为淋过雨,所以更懂得为别人撑伞,这也是我写这篇文章的原因。

下面进入正题:

0-1背包问题是最经典的dp(动态规划)问题,但是拿它当dp入门感觉还是有点难。而且这个名字经常会误导一些人,有些人看到题目里出现背包二字,就觉得这题是dp问题,实际上dp是一类问题,这类问题往往是在某种条件下找到最优解。

关于这类解,我们需要按照下面的思路:

1.怎么表示当前的状态

2.状态转移方程是什么

3.时间复杂度能否满足

这里可以先看一下点击这里,感受一下动态规划的优点:减少重复计算,降低时间复杂度

那么接下来我们看看0-1背包问题怎么写呢?

题目如下

题目描述

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

第 i 件物品的体积是 v i v_i vi,价值是 w i w_i wi

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

输入格式

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

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

输出格式

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

数据范围

0<N,V≤1000

0< v i , w i v_i,w_i vi,wi≤1000

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

8

思路

我们可以看到数据为0~1000

如果用dfs的话,数据量是 2 1000 2^{1000} 21000,数据量大的惊人,如果没有时间限制,我们让计算机一直跑,肯定会出结果的(反正不是我跑)。但是作为小镇做题家,我们是有时间限制的,那么我们怎么做呢?

我们首先定义两个一维数组,分别为w [ ] ,V[ ];这两个数组分别代表价值(worth)和容量(volume)

那接下来按照我们上面写的思路:

1:首先

我们怎么来表示当前的状态呢?我们先思考一下,我们每次选择物品都有两种选择,要么选要么不选。然后有什么限制条件呢?那当然是选的时候,我们得先看看容量够不够啦,容量不够,想放进去也放不进去QAQ。这里通过各位的一顿分析,可以知道有两个变量,也就是

1:选或不选

2:选了容量够不够

这时候我们就可以用一个二维数组来表示一下当前的状态。

我们用dp[i][j]来表示当判断到第i个的时候,我们剩余j个容量,这时候dp[i][j]代表的是这时候我们背包里面物品里面的最大价值。

2:其次

我们该思考一下转移状态方程啦。那我们思考一下状态转移方程是什么呢?我们很容易想到我们判断第i个物品的时候,前i-1个物品我们都判断过了,这时候第i个物品有两种选择

1:不选

2:选

那么我们接下来看一下

第一种情况

也就是不选的情况,不选的话,我们当前的背包的容量和背包里面物品的价值都不会变,也就是

dp[i][j]=dp[i-1][j];

第二种情况

也就是选的情况,选的时候我们得先看看这个背包够不够格对吧,那么我们就需要检查一下背包的容量,背包的容量上面我们定义的是dp[i][j]中的j的值,这时候我们判断的条件是什么呢?很明显就是第i个物品的重量和这时候背包的容量j相比较,如果当前背包的剩余容量可以装下这个物品,也就是j>v[i],这时候我们就可以将它装入了,装入之后我们的体积就需要减少v[i],剩余容量为j-v[i]。这时候价值为dp[i][j]+w[i]。这时候转移方程为

dp[i][j]=dp[i][j-v[i]]+w[i];

到这里就完了嘛?不,还没有完,我们再继续思考一下,貌似有好多种不同的选择方法会走到dp[i][j]这一步,那么我们怎么办呢,当然是取最优的啦,也就是

dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i]);

最后我们考虑一下时间复杂度是多少呢?很明显是O(m*n)。我们看一下给的数据最大为1000,当m和n取最大值的话,最大的时间复杂度也只是 1 0 6 10^6 106,完全满足题目给的要求。

下面我们附上代码:

#include<iostream>
using namespace std;

const int N=1010;
int w[N];
int dp[N][N];
int v[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)
        {
            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;
}

这里的核心代码就是

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]);

那么我们能否再将代码进行优化呢?

答案是可以的,我们可以进行一边输入一边处理。

那么我们又回到了最上面的步骤,既然说了可以优化,那么我们怎么优化呢?我们可以发现上面有一段循环是多余的,就是j在0到v[i]是多余的,这个我们就是处于待优化的状态。既然这一段循环是多余的,那么dp[i][0]~dp[i][v[i]]这段空间我们也可以优化掉,那我们接下来就开始优化啦!

首先

我们想一想这个状态怎么表示呢?我们想到每次输入的时候,这时候只和当前的输入的重量和体积有关,和当前是第多少个物体没有关系,所以我们可以优化掉i的那一层,也就是只用一个一维数组来维护一下当前的状态。也就是dp[j]。那么其中j和dp[j]的含义是什么呢?我们可以思考一下,我们当前只有两个变量,分别是背包的剩余容量和背包的价值,那么根据上面的思路可得,j代表的是当前背包的容量,dp[j]是当前背包的价值。

状态表示有了,状态转移方程是什么呢?我们可以想到上面我们说的,“那么dp[i][0]~dp[i][v[i]]这段空间我们也可以优化掉” 这句话,那么我们怎么优化掉呢?我们只需要对大于当前的价值的dp进行处理,怎么处理呢?

还是有两种情况:

1:不选

2:选

那么这和上面的代码的核心代码差不多,只需要将上面的核心的代码的i给删掉就行了,也就是

dp[j]=max(dp[j],dp[j-v]+w);

最后我们思考一下该代码的时间复杂度是多少呢?很明显是小于O(nm)的,上面的O(nm)都行,那这个代码更行了。同时这段代码优化了一层,空间复杂度由原来的O( n 2 n^2 n2)变成了O(n),直接降了一个度。

代码奉上:

#include<iostream>
using namespace std;
int dp[10010];
int main()
{
    int N,M;
    cin>>N>>M;
    //定义状态dp[i]就是重量为i,价值为dp[i]
    for(int i=0;i<N;++i)
    {
        int v,w;
        cin>>v>>w;
        for(int j=M;j>=v;--j)
         dp[j]=max(dp[j],dp[j-v]+w);
    }
    cout<<dp[M]<<endl;
    return 0;
}

你以为到这里就完了吗?错啦,这里有个我们需要思考的地方,为什么j从m到v,而不是v到m呢?这里就需要读者自己思考啦。

提示:
如果从v到m的话,如果先更新dp[v]的话,dp[v]的值可能会变,然后如果更新dp[v+v]的话可能会重复计算,就提示到这啦,剩下的只能靠读者去悟啦!

如果实在不懂的话我举个例子:

假设有三件物品,背包最大容量为2,价值分别为1,2,3,而它们的体积都是1,这时候我们知道最大价值为3+2=5,而如果j从v到m的话,最后得出来的结果是6,这里需要读者自己模拟一下啦!

至此,本菜鸡花两个小时写的0-1背包问题到此结束!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值