01背包总结-快速理解

在csdn上看了很多关于01背包的问题,感觉这样说理解的比较快(也不能说理解吧,应该是大体知道这个公式是怎么运作的)

(这是作为一只菜鸡原来只知道模板,过程啥都不知道,只是知道这样算就是对的,现在终于知道它是怎么运作的,希望你也能有所收获,)

希望你能耐心的看完,以前我看别人的博客也是一扫而过,感觉字太多,太深奥,就不想看,这篇绝对是菜鸡式入门

0-1 背包问题:给定 n 种物品和一个容量为 C 的背包,物品 i 的重量是 wi,其价值为 vi 。

问:应该如何选择装入背包的物品,使得装入背包中的物品的总价值最大?

分析一波,面对每个物品,我们只有选择拿取或者不拿两种选择,不能选择装入某物品的一部分,也不能装入同一物品多次。

解决办法:声明一个 大小为  m[n][c] 的二维数组,m[ i ][ j ] 表示 在面对第 i 件物品,且背包容量为  j 时所能获得的最大价值 ,那么我们可以很容易分析得出 m[i][j] 的计算方法,

1). j < w[i] 的情况,这时候背包容量不足以放下第 i 件物品,只能选择不拿

m[ i ][ j ] = m[ i-1 ][ j ]

2). j>=w[i] 的情况,这时背包容量可以放下第 i 件物品,我们就要考虑拿这件物品是否能获取更大的价值。

如果拿取,m[ i ][ j ]=m[ i-1 ][ j-w[ i ] ] + v[ i ]。 这里的m[ i-1 ][ j-w[ i ] ]指的就是考虑了i-1件物品,背包容量为j-w[i]时的最大价值,也是相当于为第i件物品腾出了w[i]的空间。

如果不拿,m[ i ][ j ] = m[ i-1 ][ j ] , 同(1)

究竟是拿还是不拿,自然是比较这两种情况那种价值最大。

由此可以得到状态转移方程

if(j>=w[i])
    m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
else
    m[i][j]=m[i-1][j];

例:0-1背包问题。在使用动态规划算法求解0-1背包问题时,使用二维数组m[i][j]存储背包剩余容量为j,可选物品为i、i+1、……、n时0-1背包问题的最优值。绘制
价值数组v = {8, 10, 6, 3, 7, 2},

重量数组w = {4, 6, 2, 2, 5, 1},

背包容量C = 12时对应的m[i][j]数组。

0123456789101112
1000888888888
20008810101010181818
30668814141616181824
40669914141717191924
50669914141717192124
626691114161719192124

下面的希望大家能不厌其烦的把下面两段看完,(以前我连图都看不懂,现在菜鸡的我终于能够看懂图啦,还能懂一点它们的过程)

(第一行和第一列为序号,其数值为0)
如m[2][6],在面对第二件物品,背包容量为6时我们可以选择不拿,那么获得价值仅为第一件物品的价值8,如果拿,就要把第一件物品拿出来,放第二件物品,价值10,那我们当然是选择拿。m[2][6]=m[1][0]+10=0+10=10;依次类推,得到m[6][12]就是考虑所有物品,背包容量为C时的最大价值。

随便从里面抽一个数,比如m[4][8]=17怎么来的?先看m[3][8]=16,再看m[3][6]=14加上第四个物体的重量3就是17啦

也就是说遍历到那一个物体的时候,先在背包中腾出它所要的空间(上述的例子第四个物体需要2个空间才能装),剩下的空间保持在最大的价值(上述的例子就是m[3][6]=14),把这个物体再装进去,计算它的总价值(上述例子中也就是17)那这个装了这个物体的价值与未装这个物体的最大价值进行比较(也就是上述例子中的16)(当然它们比较的前提是相同容积状态下进行的比较)这样一直到最后最后一个肯定是最大价值啦

#include <iostream>
#include <cstring>
using namespace std;
 
 
const int N=15;
 
 
int main()
{
    int v[N]={0,8,10,6,3,7,2};
    int w[N]={0,4,6,2,2,5,1};
 
 
    int m[N][N];
    int n=6,c=12;
    memset(m,0,sizeof(m));
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=c;j++)
        {
            if(j>=w[i])
                m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]);
 
 
            else
                m[i][j]=m[i-1][j];
        }
    }
 
 
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=c;j++)
        {
            cout<<m[i][j]<<' ';
        }
        cout<<endl;
    }
 
 
    return 0;
}

由二维降为一维又怎样理解

memset(dp, 0, sizeof(dp));
for(int i=0; i<n; i++){
    for(int j=v; j>=vol[i]; j--){
    dp[j] = max(dp[j], dp[j-vol[i]]+val[i])
    }
}

如何理解二维降一维呢?对于外层循环中的每一个i值,其实都是不需要记录的,在第i次循环时,所有的dp[0…v]都还未更新时,dp[j]还记录着前i-1个物品在容量为j时的最大价值,这样就相当于还记录着dp[i-1][j]和dp[i-1][j-vol[i]]+val[i]。

为什么要从v开始递减遍历?我举个例子,假设一个物品GG价值1000,体积为2,那么假设我们按【0…..v】这个顺序遍历,那么在j=2时,dp[2] = max(dp[2], dp[0]+1000),那么dp[2] = 1000,当j=4时,dp[4]=max(dp[4], dp[2]+1000), dp[4] = 2000,这时我们再思考一下,GG将被放进背包两次!!,如果我们逆序遍历,就可以避免这种结果。

此外,这里可以进行一个常数优化,将j>=vol[i]写进for循环中。

以下是具体的背包总结

题意: n个物品,v[]表示体积,w[]表示价值,m表示背包容量。求背包可得最大价值
初始化:
若要求为恰好装满,则初始化f[0] = 0,f[1...n] = INT_MIN,求答案时需要遍历最后一行取最大值。
若不要求,则f[0...n] = 0,答案为f[m]。
1.01背包问题
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1005;
int v[N],w[N];
int f[N][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 = 1; j <= m; j ++){
            if(j>=v[i])
                f[i][j] = max(f[i-1][j],f[i-1][j-v[i]]+w[i]); 
            else{
                f[i][j] = f[i-1][j];
            }
        }
    }
    cout<<f[n][m]<<endl;
    return 0;
}

  空间一维优化(第二层循环逆序m....v[i])

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1005;
int v[N],w[N];
int f[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 = m; j >= 1 && j>=v[i]; j --){
                f[j] = max(f[j],f[j-v[i]]+w[i]); 
        }
    }
    cout<<f[m]<<endl;
    return 0;
}
 

2.完全背包问题
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1005;
int v[N],w[N];
int f[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 = v[i]; j <=m; j ++){                // 逆序
                f[j] = max(f[j],f[j-v[i]]+w[i]); 
        }
    }
    cout<<f[m]<<endl;
    return 0;
}

3.多重背包问题
1.朴素做法(01背包变形,复杂度n^3)

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 105;
int v[N],w[N],s[N],f[N];
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i = 1;i <= n;i ++) cin>>v[i]>>w[i]>>s[i];
    for(int i = 1; i <= n; i ++){
        for(int j = m; j >= v[i]; j--){
            for(int k = 1; k * v[i] <= j && k <= s[i]; k ++){
                f[j] = max(f[j],f[j - k * v[i]] + k*w[i]);
            }
        }
    }
    cout<<f[m]<<endl;
    return 0;
}

2.二进制优化

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;

const int N = 2050;
int f[N];

struct Goods{
  int v,w;
};

int main()
{
    int n,m,v,w,s;
    vector<Goods> goods;
    cin>>n>>m;
    for(int i = 0 ; i < n; i ++){
        cin>>v>>w>>s;
        for(int j = 1 ; j <= s ; j *= 2){
            s -= j;
            goods.push_back({v*j,w*j});
        }
        if(s > 0) goods.push_back({v*s,w*s});
    }
    for(int i = 0 ; i < goods.size(); i ++){
        for(int j = m ;j >= goods[i].v; j-- ){
            f[j] = max(f[j],f[j - goods[i].v] + goods[i].w);
        }
    }
    cout<<f[m]<<endl;
    return 0;
}

4.混合背包问题
tips:先归类为01背包和完全背包,再进行动态规划

s:代表物品种类,若s == -1,为01背包,s == 0,为完全背包,s > 0 ,为多重背包

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;

const int N = 1010;
int f[N];
struct Goods{
    int s;
    int v,w;
};

int main()
{
    vector<Goods> goods;
    int n,m;
    cin>>n>>m;
    int s,v,w;
    for(int i = 0; i < n; i ++ ){
        cin>>v>>w>>s;
        if(s == -1){
            goods.push_back({-1,v,w});
        }
        else if( s == 0){
            goods.push_back({0,v,w});
        }
        else{
            for(int k = 1 ; k <= s; k *= 2){
                s -= k;
                goods.push_back({-1, k*v,k*w});
            }
            if(s > 0) goods.push_back({-1,s*v,s*w});
        }
    }
    for(int i = 0; i < goods.size(); i ++){
        if(goods[i].s == -1){
            for(int j = m; j >= goods[i].v; j--)   f[j] = max(f[j],f[j - goods[i].v]+goods[i].w);
        }
        else{
            for(int j = goods[i].v ;j <= m; j ++)  f[j] = max(f[j],f[j - goods[i].v]+goods[i].w);
        }
    }
    cout<<f[m]<<endl;
    return 0;
}

5.二维费用的背包问题
f[i][j] 代表体积为i,质量为j的状态的最优解
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 110;
int f[N][N];

int main()
{
    int n,V,M;
    cin>>n>>V>>M;
    for(int i = 0;i < n; i ++){
        int v,m,w;
        cin>>v>>m>>w;
        for(int j = V;j >= v;j --){
            for(int k = M;k >= m; k-- ){
                f[j][k] = max(f[j][k],f[j-v][k-m] + w);
            }
        }
    }
    cout<<f[V][M]<<endl;
    return 0;
}                 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值