一些背包问题

1.01背包问题

动态规划算法(DP)_那什-CSDN博客_dp算法

这里是动态规划算法的一篇博客,讲得比较基础而且容易理解。

其实我之前理解一直有误,这里换一种说法解释一下方便理解。

f[i][j]指的是在有i件物品和j容量的情况下。这里一定要注意j表示的不是当前的剩余容量而是当前总的容量是多少!由此可得当状态转移时,f[i-1][j-w[i]]指的是当有i-1件物品时容量为j-w[i]的情况,也是在当时情况下取得的最大价值!

在二维数组dp的方法中进行初始化,一开始只有f[0][0]是合法的、需要进行初始化的。可选物体为0、体积为0的情况下,最大价值为0.

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

第 ii 件物品的体积是 vivi,价值是 wiwi。

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

输入格式

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

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

输出格式

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

#include<iostream>
#include<cstirng>
#include<algorithm>
​
using namespace std;
​
const int N=1010;
​
int n,m;//n表示物品个数,m表示总共可用的背包容量
ingf[N][N];
int v[N],w[N];//分别记录每个物品的体积和价值
​
int main(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    
    for(i=1;i<=n;i++){
        for(int j=0;j<=m;j++){
            f[i][j]=f[i-1][j];//第i件物品不选
            if(j>=v[i])//当剩余体积大于第i件物品的体积才能选
                f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);//状态转移方程了
        }
    }
    
    int res=0;
    for(int i=0;i<=m;i++) res=max(res, f[n][i]);
    
    cout<<res<<endl;
    return 0;
}

上面是朴素做法。然后进行优化。

由代码可知每个f[i][j]都只跟它的前一层f[i-1][j]有关,所以我们没必要把每一层都记下来,我们可以用滚动数组或者一维数组来表示。

接下来演示的是如何用一维数组进行优化

#include <iostream>
#include <algorithm>
​
using namespace std;
​
const int N = 1010;
​
int n, m;
int v[N], w[N];
int f[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 = m; j >= v[i]; j -- )
            f[j] = max(f[j], f[j - v[i]] + w[i]);
​
    cout << f[m] << endl;
​
    return 0;
}
​
作者:yxc
链接:https://www.acwing.com/activity/content/code/content/57785/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

可以看到其中做出了更改的部分只有把f[i][j]改为了用一维数组来存,进行了一个等价变化,那么怎么理解呢。

首先,我们考虑物品只存在i个的情况,而i从1到n。而在这种情况中我们背包的容量从m到v[i]进行考虑。注意j的遍历改为了从大到小。为什么?

因为我们的一维数组,每次只存了一层(即物品为i个)的情况。f[j]的结果是从f[j]和f[j-v[i]]+w[i]中取得最大,由等价变化可知,我们期望的f[j]和f[j-v[i]]都是i-1层的结果。

假设我们刚进入j的循环,从最大容量j开始遍历,此时整个一维数组尚未经过更新,仍然是i-1层的结果,当我们考虑容量为j-v[i]的情况时,可知j-v[i]是比j更小的数,我们就会到一维数组靠前的数去寻找这个值,而这个未经过更新的值实际上就是f[i-1][j-v[i]]的值。

假如我们从小到大遍历,则f[j]就会从小到大更新为第i层的情况,那么在之后我们尝试取得f[j-v[i]]的值时,朝前去寻找得到的实际上是f[i][j-v[i]]

2.完全背包问题

完全背包问题跟01背包问题的区别在于完全背包问题里的每一个物品可以用无限次。而它相对于01背包的代码只需要修改一个地方:把j的循环从大→小改为从小→大

#include <iostream>
#include <algorithm>
​
using namespace std;
​
const int N = 1010;
​
int n, m;
int v[N], w[N];
int f[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 = v[i]; j <=m; j ++ )//只改了这个
            f[j] = max(f[j], f[j - v[i]] + w[i]);
​
    cout << f[m] << endl;
​
    return 0;
}
 

分析:

01背包针对一个物体只有选或者不选两个情况,但是完全背包问题我们需要考虑当容量j够用时,选择0个、1个、2个……k个i物体的情况,所以

f[i][j]=max(f[i-1][j],f[i-1][j-v]+w,f[i-1][j-2v]+2w……)

那么,f[i][j-v]的情况又如何呢?

f[i][j-v]=max(f[i-1][j-v],f[i-1][j-wv]+w,f[i-1][j-3v]+2w……)

两个式子max括号里的重合度很高,唯一的区别在于下面的式子每一项比上面从第二项开始少加了一个w。所以为了优化,我们进行等价替换的时候,可以认为:

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

这个时候,我们再优化为一维数组,f[j-v[i]]期望得到的值就是第i个物品的情况了。所以把从大到小遍历改为从小到大遍历(为什么可以结合上面的01背包问题进行思考)。

3.多重背包问题

相比上面两种背包问题,区别在于对每件物品做了个数限制,最多有Si件。

4.分组背包问题

N组物品和容量是V的背包,每组物品有若干个,同一组内的物品最多只能选一个。每件物品的体积是Vij,价值是Wij,i是组号,j是组内编号。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值