动态规划--基础背包问题

动态规划–基础背包问题

y总yyds!!

01背包

每个物品最多只用 一次

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

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

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

01背包问题Dp
状态表示 f(i,j)
集合 (所有选法)
只从前 i 个物品选
选出物品总体积不超过 j
属性 Max
状态计算
集合的划分(第i个物品选0个或者1个)
f(i,j) =Max( f( i-1 , j) , f( i-1 , j-vi ) + wi)
  • (i,j)为从前i个物品中选,总体积不超过j的所有选法集合

  • f(i,j)的值为从前i个物品中选,总体积不超过j的所有选法集合中价值最大的数

  • 集合划分

  • 状态计算公式f(i,j) =Max( f(i-1 , j) , f(i-1 , j-vi) + wi )

#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N];
int f[N][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 = 0; j <=m; j ++ ){
            f[i][j]=f[i-1][j];
            if(j>=v[i])f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
        }
    }
    cout<<f[n][m]<<endl;
}

滚动数组优化

由于f[i]层只用到了f[i-1]

f(i,j) =Max( f(i-1 , j) , f(i-1 , j-vi) + wi )

i层需要i-1层的数据,j列需要的也都是小于等于j的数据

其次最终只需要f(n,v)的值,之前计算的结果不需要

因此可以不断覆盖之前的结果,降低维度,省内存

在这里插入图片描述

#include<bits/stdc++.h>
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;
}

完全背包

每件物品有无限个

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

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

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

完全背包问题Dp
状态表示 f(i,j)
集合 (所有选法)
只从前 i 个物品选
选出物品总体积不超过 j
属性 Max
状态计算
集合的划分(第i个物品选0个,1个,2个…… k-1个,k个)
  • (i,j)为从前i个物品中选,总体积不超过j的所有选法集合

  • f(i,j)的值为从前i个物品中选,总体积不超过j的所有选法集合中价值最大的数

  • 集合划分
    在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N];
int f[N][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=0;j<=m;j++)
            for(int k=0;k*v[i]<=j;k++)
                f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);
    
    cout<<f[n][m]<<endl;
    return 0;
}

减去一层循环 优化

在这里插入图片描述

  • 如图所示f[i,j-v]+w等同于Max(f[i-1,j-v]+w,f[i-1,j-2v],f[i-1,j-3w]+3w……)

  • 故可优化状态计算公式为f[i,j]=Max(f[i-1][j],f[i,j-v]+w)

  • 集合划分称为(第i种物品选0个的所有选法 || 第i个物品先选1个的所有选法)

    • 直观上可以理解为(第i个物品先选1个的所有选法)集合包括了所有(第i个物品先选k(k>0)个的所有选法)集合
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N];
int f[N][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=0;j<=m;j++){
            f[i][j]=f[i-1][j];
            if(j>=v[i]) 
                f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);
        }
    cout<<f[n][m]<<endl;
    return 0;
}
配合滚动数组再次优化

根据状态计算公式为f[i,j]=Max(f[i-1][j],f[i,j-v]+w)

可以简化为f[j]=Max(f[j],f[j-v]+w)

这个不同于01背包,由于计算f[i][j]时需要第i层的j-w列(小于j )的数据

所以每一层要从左往右计算

在这里插入图片描述

#include<bits/stdc++.h>
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;
}

多重背包

每个物品有si个,数量不同

有 N 种物品和一个容量是 V 的背包。

第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。

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

朴素算法

类似于完全背包问题

多重背包问题Dp
状态表示 f(i,j)
集合 (所有选法)
只从前 i 个物品选
选出物品总体积不超过 j
属性 Max
状态计算
集合的划分(第i个物品选k个)k<=si
f(i,j) =Max( f( i-1 , j-k*vi ) + k*wi) k<=si
  • (i,j)为从前i个物品中选,总体积不超过j的所有选法集合

  • f(i,j)的值为从前i个物品中选,总体积不超过j的所有选法集合中价值最大的数

  • 集合划分

#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int n,m;
int v[N],w[N],s[N];
int f[N][N];

int main(){
    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=0;j<=m;j++)
            for(int k=0;k<=s[i]&&k*v[i]<=j;k++)
                f[i][j]=max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);
    
    cout<<f[n][m]<<endl;
    return 0;
}

无法模仿完全背包的优化

既然朴素版本类似于完全背包问题,那么优化方式是否类似于完全背包一样呢?

完全背包优化最关键的是f[i,j-v]+w等同于Max(f[i-1,j-v]+w,f[i-1,j-2v],f[i-1,j-3w]+3w……f[i-1][j-k*v]+k*w) k*v<=j这个等价。

多重背包问题中此条件不成立

在这里插入图片描述

主要原因是k的范围带来的问题

在完全背包内,一种物品的数量是无限的,唯一的约束条件的k*v<=j

而在多重背包内,约束条件有k<=sik*v<=j,这便会导致项与项无法完全对应,故也无法等价了

另外从直觉上的解释

在完全背包中,f[i,j-v]+w可以理解为至少选一个i种物品的所有选法集合

而在多重背包内f[i,j-v]+w便无法这么解释,因为f[i,j-v]这个集合内包含选择了sii种物品的情况,再加wi就等同于存在选择了si+1i种物品的情况,超出限制。

因此优化需要选择其他思路。

二进制优化方法

在朴素做法中,往往是一个一个的枚举,从0枚举到s。

但可以使用另一种方式进行枚举,使用二进制的方式来考虑,换一个方式思考有n种物品,每种物品有si个的条件。

在这里插入图片描述

首先一个关键步骤就是打包,与其一个一个的取,不如打包起来,可以直接拿。

在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
const int N = 12010, M = 2010;
//2000*log2000

int n, m;
int v[N], w[N];
int f[M];

int main(){
    cin>>n>>m;
    int cnt=0;
    
    for(int i=1;i<=n;i++){
        int a,b,s;
        cin>>a>>b>>s;
        int k=1;
        //二进制拆分
        while(k<=s){
            cnt++;
            v[cnt]=a*k;//k个物品包在一起的新包裹
            w[cnt]=b*k;
            s-=k;
            k*=2;
        }
        //如果有空隙,就在补上 (2^[lgs]到s的距离)
        if(s>0){
            cnt++;
            v[cnt]=a*s;
            w[cnt]=b*s;
        }
    }
    n=cnt;//通过01背包算法取出前面打包的包裹
    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;
}

分组背包

物品分组,每组物品内有若干种,每一组内只能选一种

有 N 组物品和一个容量是 V 的背包。

每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号 j 是组内编号。

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

输出最大价值。

算法思路于之前多重背包问题的朴素算法较为类似

分组背包问题Dp
状态表示 f(i,j)
集合 (所有选法)
只从前 i 组中选物品
选出物品总体积不超过 j
属性 Max
状态计算
集合的划分(第i组物品选第k个)
f(i,j) =Max( f( i-1 , j-k*v[i][k] ) + k*w[i][k])
  • 状态计算公式f(i,j) =Max( f( i-1 , j-k*v[i][k] ) + k*w[i][k])
#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int n,m;
int v[N][N],w[N][N],s[N];
int f[N];

int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>s[i];
        for(int j=0;j<s[i];j++)
            cin>>v[i][j]>>w[i][j];
    }
    for(int i=1;i<=n;i++)
        for(int j=m;j>=0;j--)
            for(int k=0;k<s[i];k++)
                if(v[i][k]<=j)
                    f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
    cout<<f[m]<<endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值