算法|动态规划|背包dp

动态规划|背包dp

1.01背包问题
2.完全背包问题
3.多重背包问题
4.多重背包问题(二进制优化)
5.混合背包问题
6.二维费用背包问题
7.分组背包问题

心有猛虎,细嗅蔷薇。你好朋友,这里是锅巴的C\C++学习笔记,常言道,不积跬步无以至千里,希望有朝一日我们积累的滴水可以击穿顽石。
在这里插入图片描述

背包dp的动态转移方程dp[j]=max(dp[j],dp[j-w[i]]+v[i])

前言:如问题给出体积和价值,我们设vw,如果是重量和价值,我们设wv(个人喜好),下不再解释。

01背包问题

题目

输入描述:
在这里插入图片描述
在这里插入图片描述

输出描述:
在这里插入图片描述

示例1
输入
8 5
3 4
5 5
1 2
2 1
2 3
输出
10

注意
01背包问题即选或不选的问题。

二维横轴代表背包体积(或重量),纵轴代表物品个数。

N\M012345678
0000000000
1000444444
2000444449
3022666666
4022337777
502355771010

实践代码:

#include <bits/stdc++.h>  
using namespace std;  
#define int long long
#define endl '\n' 
int dp[1010];//一维滚动数组
int v[1010],w[1010];//物品的价值value和重量weight
void solve(){
  int m, n; cin >> m >> n; // 背包容量和数量    
  for(int i = 1; i <=n; i++) cin >> w[i] >> v[i];  
  
  for(int i=1;i<=n;i++){
    for(int j=m;j>=w[i];j--){//从后往前遍历
      dp[j]=max(dp[j],dp[j-w[i]]+v[i]);//状态转移方程
    }
  }  
  cout << dp[m]<<endl; 
} 
signed main()  
{  
  ios::sync_with_stdio(false);
  cin.tie(0);cout.tie(0);
  int t=1;
  //cin>>t;
  while(t--){
    solve();
  }
  return 0;  
}

优化:
我们注意到在处理数据时,我们是一个物品一个物品,一个体积一个体积的枚举。

因此我们可以不必开两个数组记录体积和价值,而是边输入边处理。

const int N = 1010;
int dp[N];
void solve(){
    int n,m;cin>>m>>n;
    for(int i=1;i<=n;i++){
        int w,v;cin>>w>>v;
        for(int j=m;j>=w;j--) dp[j] = max(dp[j],dp[j-w]+v);
    }
    cout<<dp[m]<<endl;
}

完全背包问题

题目
在这里插入图片描述

输入描述:
在这里插入图片描述

输出描述:
在这里插入图片描述

示例1
输入
8 5
3 4
5 5
1 2
2 1
2 3
输出
16

注意
完全背包比01背包复杂的一点是,物品是无限个的。

N\M012345678
0000000000
1000444888
2000444888
30246810121416
40246810121416
50246810121416

实践代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
int dp[1001];//一维滚动数组
int v[1001],w[1001];//物品的价值value和重量weight
void solve(){
  int m,n;cin>>m>>n;//背包容量和数量
  for(int i=1;i<=n;i++) cin>>w[i]>>v[i];
  
  for(int i=1;i<=n;i++){
    for(int j=w[i];j<=m;j++){//从前往后遍历
       dp[j]=max(dp[j],dp[j-w[i]]+v[i]);//状态转移方程
    }
  } 
  cout<<dp[m]<<'\n';
}
signed main()
{
  ios::sync_with_stdio(false);
  cin.tie(0);cout.tie(0);
  int t=1;
  //cin>>t;
  while(t--){
    solve();
  }
  
  return 0;
}

优化:

const int N = 1010;
int dp[N];
void solve(){
    int n,m;cin>>m>>n;
    for(int i=1;i<=n;i++){
        int w,v;cin>>w>>v;
        for(int j=w;j<=m;j++) dp[j] = max(dp[j],dp[j-w]+v);
    }
    cout<<dp[m]<<endl;
}

多重背包问题

题目
有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入描述:
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

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

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

注意
多重背包就是si个01背包,与完全背包的区别是,它的物品个数是有限的。

实践代码:

const int N = 1e5+10;
//注意dp数组开大一点,至少比si*n大,因为我们需要拆解物品,最终个数:n种物品*个数(si)必然 ≥ n
int dp[N],a[N],b[N];
void solve(){
    int n,m,t=0;cin>>n>>m;
    while(n--){
        int v,w,s;cin>>v>>w>>s;
        while(s--){//将s个物品拆成一个个独立的物品
            a[++t]=v;//体积
            b[t]=w;//价值
        }
    }
    for(int i=1;i<=t;i++){//01背包
        for(int j=m;j>=a[i];j--) dp[j]=max(dp[j],dp[j-a[i]]+b[i]);
    }
    cout<<dp[m];
}

优化:

const int N = 1e5+10;
int dp[N],a[N],b[N];
void solve(){
    int n,m;cin>>n>>m;
    while(n--){
        int v,w,s;cin>>v>>w>>s;
        while(s--){
            for(int j=m;j>=v;j--) dp[j]=max(dp[j],dp[j-v]+w);
        }
    }
    cout<<dp[m];
}

多重背包问题(二进制优化)

题目
有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。

输入描述:
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。

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

数据范围:
0 < N ≤ 1000
0 < V ≤ 2000
0 < vi,wi,si ≤ 2000

提示:
本题考查多重背包的二进制优化方法。

示例1
输入
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出
10

注意
二进制优化,即将第i种物品的总数si个分成20,21,22…的小组,如有剩余则再添加一组,再把这些组当作一个个物品来选择取或不取。简单来说就是把物品按照个数1,2,4,8…分别放在不同的箱子里,再把这n个箱子当成n个物品,这样就又转化为了01背包问题。

实践代码:

const int N = 1e5+10;
int dp[N],v[N],w[N];
void solve(){
    int n,m;cin>>n>>m;
    int cnt=0;//组别数
    
    while(n--){
        int a,b,s;cin>>a>>b>>s;
        int k=1;
        while(s>=k){
            v[++cnt]=a*k;//第cnt组的体积
            w[cnt]=b*k;//第cnt组的价值
            s-=k;
            k<<=1;//k=k*2
        }
        if(s){//如物品个数还有剩余,则自成1组(再添加一组)
            v[++cnt]=a*s;
            w[cnt]=b*s;
        }
    }
    
    for(int i=1;i<=cnt;i++){//01背包
        for(int j=m;j>=v[i];j--) dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
    }
    cout<<dp[m];
}

优化:

const int N = 1010;
int dp[N];
void solve(){
    int n,m,cnt=0;cin>>n>>m;
    while(n--){
        int v,w,s;cin>>v>>w>>s;
        int k=1;
        while(s>=k){
            for(int j=m;j>=k*v;j--) dp[j]=max(dp[j],dp[j-k*v]+k*w);
            s-=k;
            k<<=1;
        }
        if(s){for(int j=m;j>=s*v;j--) dp[j]=max(dp[j],dp[j-s*v]+s*w);}
    }
    cout<<dp[m];
}

混合背包问题

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

物品一共有三类:

第一类物品只能用1次(01背包);第二类物品可以用无限次(完全背包);第三类物品最多只能用 si 次(多重背包);每种体积是 vi,价值是 wi

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

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

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

  • si=−1 表示第 i 种物品只能用1次;
  • si=0 表示第 i 种物品可以用无限次;
  • si>0 表示第 i 种物品可以使用 si 次;

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

数据范围:
0<N,V≤1000
0<vi,wi≤1000
−1≤si≤1000
示例1
输入
4 5
1 2 -1
2 4 1
3 4 0
4 5 2
输出
8

注意
混合背包即将前面三种的背包问题混合起来,有的只能取一次,有的能取无限次,有的只能取k次。

伪代码:

//混合背包
 for(循环物品种类){
        if(是01背包) 套用01背包模板;
        else if(是完全背包) 套用完全背包模板;
        else if(是多重背包) 套用多重背包模板;
    }

实践代码:

const int N = 1010;
int dp[N];
void solve(){
    int n,m,cnt=0;cin>>n>>m;
    for(int i=1;i<=n;i++){
        int v,w,s;cin>>v>>w>>s;
        if(s==-1){//01背包
            for(int j=m;j>=v;j--) dp[j]=max(dp[j],dp[j-v]+w);
        }
        if(s==0){//完全背包
            for(int j=v;j<=m;j++) dp[j]=max(dp[j],dp[j-v]+w);
        }
        if(s>0){//多重背包(二进制优化)
            int k=1;
            while(s>=k){
                for(int j=m;j>=k*v;j--) dp[j]=max(dp[j],dp[j-k*v]+k*w);
                s-=k;
                k<<=1;
            }
            if(s) {for(int j=m;j>=s*v;j--) dp[j]=max(dp[j],dp[j-s*v]+s*w);}
        }
    }
    cout<<dp[m];
}

二维费用背包问题

题目
有 N 件物品和一个容量是 V 的背包,背包能承受的最大重量是 M。每件物品只能用一次。体积是 vi,重量是 mi,价值是 wi
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。
输出最大价值。

输入描述:
第一行三个整数,N,V,M,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。
接下来有 N 行,每行三个整数 vi,mi,wi,用空格隔开,分别表示第 i 件物品的体积、重量和价值。

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

数据范围:
0<N≤1000
0<V,M≤100
0<vi,mi≤100
0<wi≤1000
示例1
输入
4 5 6
1 2 3
2 4 4
3 4 5
4 5 6
输出
8

注意
二维费用背包就是多一维的01背包,多一个重量限制,那我们照葫芦画瓢,之前的01背包是一维滚动数组,现在我们开到二维。

实践代码:

const int N = 1010;
int dp[N][N],v[N],m[N],w[N];
void solve(){
    int n,V,M;cin>>n>>V>>M;
    for(int i=1;i<=n;i++){
        cin>>v[i]>>m[i]>>w[i];
    }
    for(int i=1;i<=n;i++){
        for(int j=V;j>=v[i];j--){//体积
            for(int k=M;k>=m[i];k--){//重量
                dp[j][k]=max(dp[j][k],dp[j-v[i]][k-m[i]]+w[i]);
            }
        }
    }
    cout<<dp[V][M];
}

分组背包问题

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

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

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

输出最大价值。

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

接下来有 N 组数据:

  • 每组数据第一行有一个整数 Si,表示第 i 个物品组的物品数量;
  • 每组数据接下来有 Si 行,每行有两个整数 vij,wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;

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

数据范围:
0<N,V≤100
0<Si≤100
0<vij,wij≤100
示例1
输入
3 5
2
1 2
2 4
1
3 4
1
4 5
输出
8

注意
分组背包其实就是从“在所有物品中选择一件”变成了“从当前组中选择一件”,于是就对每一组进行一次01背包即可。

实践代码:

const int N = 1010;
int dp[N],v[N],w[N];
void solve(){
    int n,m,cnt=0;cin>>n>>m;
    for(int i=1;i<=n;i++){//枚举每一组
        int s;cin>>s;
        for(int j=1;j<=s;j++) cin>>v[j]>>w[j];
        for(int j=m;j>=0;j--){//枚举背包容量
            for(int k=1;k<=s;k++){//枚举该组的每一个物品
                /*如果背包容量充足*/
                if(j>=v[k]) dp[j] = max(dp[j],dp[j-v[k]]+w[k]);
            }
        }
    }
    cout<<dp[m];
}

心有猛虎,细嗅蔷薇。再见了朋友~

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值