DP——背包

目录

一、问题分析

1.01背包

2.完全背包

3.多重背包

4.分组背包

二、例题实战

1.01背包

a.题目描述

b.代码实现

2.完全背包

a.题目描述

b.代码实现

3.多重背包

a.题目描述

b.代码实现

4.分组背包

a.题目描述

b.代码实现


一、问题分析

1.01背包

01背包问题模型:给定N个物品,其中第i个物品的体积为Vi,价值为Wi。有一容积为M的背包,要求选择一些物品放入背包,使得物品总体积不超过M的前提下,物品的价值总和最大。

分析:依据其N个物品选择放入背包,对该问题进行分解,分成n类子问题,其阶段划分为当前第i个背包。其状态表示的集合为F[i,j],表示从前i个物品中选出了总体积为j的物品放入背包,物品的价值和,状态表示的属性为max。状态的转移方式由选或不选第i个物品组成。状态转移方程为F[i,j]=max(F[i-1,j],F[i-1,j-Vi]+Wi if(j>=Vi) ),0<=j<=M。初始化F[0,0]=0,所求为F[N][M]。

分析如图1所示。

图1

    for(int i=1;i<=n;i++){
        for(int j=0;j<=m;j++){
            f[i][j]=f[i-1][j]; ///不选第i个物品
            ///选择第i个物品
            if(j>=v[i]) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
        }
    }

优化:通过分析状态转移方程发现,当前状态只和i-1行的状态有关,则其转移方程可化简为F[j]=max(F[j],F[j-Vi]+Wi),j>=Vi。其中j由M枚举到Vi。因为更新方式为倒序,阶段更新由后面开始。则当前F[j]实际存储的是F[i-1][j],F[j-Vi]存储的是F[i-1][j-Vi],则F[j]的转移方程等价于二维的F[i,j]转移方程。初始化F[0]=0,所求为F[M]。

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

2.完全背包

完全背包问题:给定N种物品,其中第i个物品的体积为Vi,价值为Wi,并且有无数个。有一容积为M的背包,要求选择若干个物品放入背包,使得物品总体积不超过M的前提下,物品的价值总和最大。

分析:其阶段划分为当前选取的物品i,状态表示集合为F[i,j],表示从前i个物品中选出若干物品其总体积为j放入背包的价值总和,状态表示属性为max。阶段间的状态转移方式为,选多少个第i个物品。状态转移方程为F[i,j]=max(F[i-1,j],F[i-1,j-Vi]+Wi,F[i-1,j-2*Vi]+2*Wi,...,F[i-1,j-k*Vi]+k*Wi),0<=j<=M。初始化为F[0,0]=0,所求为F[N][M]。

分析如图2所示。

图2

    for(int i=1;i<=n;i++){
        for(int j=0;j<=m;j++){
            f[i][j]=f[i-1][j]; ///不选该物品
            for(int k=1;k*v[i]<=j;k++) ///选择k个物品
            f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
        }
    }

优化:通过观察发现,F[i,j-Vi]=max(F[i-1,j-Vi],F[i-1,j-2*Vi]+Wi,F[i-1,j-3*Vi]+2*Wi,...,F[i-1,j-k*Vi]+(k-1)*Wi)加上Wi,等价于F[i,j-Vi]=max(F[i-1,j-Vi]+Wi,F[i-1,j-2*Vi]+2*Wi,...,F[i-1,j-k*Vi]+k*Wi),则F[i,j]=max(F[i-1,j],F[i,j-Vi]+Wi)。依据01背包所分析的,F[i,j]可以去掉i,则有F[j]=max(F[j],F[j-Vi]+Wi),Vi<=j<=M。因为F[j-Vi]实际为F[i,j-Vi],是从i阶段转移过来的,故j的更新要先将前面的i-1阶段的更新为i阶段的,故遍历顺序要从Vi到M。

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

3.多重背包

多重背包问题:给定N个物品,其中第i种物品的体积为Vi,价值为Wi,并且有Ci个。有一个容积为M的背包,要求选择若干物品放入背包,使得物品总体积不超过M的前提下,物品的价值总和最大。

分析:

方法1,直接拆分法。将每种物品内的Ci个物品都当成是独立的一个物品,然后对这些独立的物品当成共有\sum_{i=1}^{N}Ci个物品的01背包问题。

for(int i=1;i<=n;i++){
    for(int k=1;k<=c[i];k++){ ///第i种物品内的Ci个物品
        for(int j=m;j>=v[i];j--){
            f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }
}

方法2,二进制拆分法。每种物品内的Ci个物品有2^{0}+2^{1}+2^{2}+...+2^{p}<=Ci,其中R为2的整次幂相加剩下来的数即R=Ci-(2^{0}+2^{1}+2^{2}+...+2^{p})。因为0~2^{p+1}-1可以由2^{0},2^{1},2^{2},...,2^{p}选择几个数相加得到。而0~2^{p+1}-1+R等价于0~Ci,可以由2^{0},2^{1},2^{2},...,2^{p},R选择几个数相加得到。故可以将每种物品内Ci个物品,分成这p+2个物品,组合而成。 

    vector<Good> goods;///goods({int,int})
    while(n--){
        int v,w,s; 
        scanf("%d%d%d",&v,&w,&s);
         ///二进制拆分物品
        for(int k=1;k<=s;k<<=1){ ///k为2的整次幂
            s-=k; ///当前物品个数减去2的整次幂
            goods.push_back({k*v,k*w});
        }
        if(s>0) goods.push_back({s*v,s*w}); ///将剩余物品加入goods中
    }
    
    for(auto x:goods){ ///将每个物品循环
        for(int j=m;j>=x.v;j--){ ///尝试选择当前物品x
            f[j]=max(f[j],f[j-x.v]+x.w);
        }
    }

4.分组背包

分组背包问题:给定N组物品,其中第i组有Ci个物品。第i组的第j个物品的体积为Vij,价值为Wij。有一容积为M的背包,要求选择如干个物品放入背包,使得每组至多选择一个物品并且物品总体积不超过M的前提下,物品的价值总和最大。

分析:将物品组数作为阶段划分,其状态表示集合为F[j],表示选择总体积为j的物品放入背包,物品的价值和,状态表示属性为max。对于每一组内的数个物品,做出的状态转移方式为不选物品或选择一个物品。故状态转移方程为F[j]=max(F[j],F[j-Vik]+Wik),1<=k<=Ci。其中因为阶段转移是由i-1转移到i,故j的遍历顺序为倒序,由M>=j>=0。

分析如图3所示。

图3

for(int i=1;i<=n;i++){
    for(int j=m;j>=0;j--){ ///因为每个背包只能选择一个物品,故要放在外层
        for(int k=1;k<=c[i];k++){
            if(j>=v[i][k]){
                f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
            }
        }
    }
}

二、例题实战

1.01背包

a.题目描述

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

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

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

输入格式

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

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

输出格式

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

数据范围

0<N,V≤1000
0<vi,wi≤1000

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

8

b.代码实现

```
///二维
#include<iostream>
#include<cstdio>

using namespace std;

const int N=1010;

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

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)    scanf("%d%d",&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];///不选物品i
             ///选择物品i
            if(j>=v[i]) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]);
        }
    }
    
    ///输出从前n个物品里面选,物品总体积为m的最大值
    cout <<f[n][m] <<endl; 
    return 0;
}
```
```
///一维
#include<iostream>
#include<cstdio>

using namespace std;

const int N=1010;

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

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)    scanf("%d%d",&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]);
        } 
    }
    
     ///输出物品总体积为m的最大值
    cout <<f[m] <<endl;
    return 0;
}
```

2.完全背包

a.题目描述

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

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

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

输入格式

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

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

输出格式

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

数据范围

0<N,V≤1000
0<vi,wi≤1000

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

10

b.代码实现

```
///二维
#include<iostream>
#include<cstdio>

using namespace std;

const int N=1010;

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

int main(){
    cin >>n >>m;
    for(int i=1;i<=n;i++)   scanf("%d%d",&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]; ///不选该物品
            for(int k=1;k*v[i]<=j;k++) ///选择k个物品
            f[i][j]=max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
        }
    }
    
    cout <<f[n][m] <<endl;
    return 0;
}
```
```
///一维
#include<iostream>
#include<cstdio>

using namespace std;

const int N=1010;

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

int main(){
    cin >>n >>m;
    for(int i=1;i<=n;i++)   scanf("%d%d",&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.多重背包

a.题目描述

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

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

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

输入格式

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

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

输出格式

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

数据范围

0<N,V≤100
0<vi,wi,si≤100

输入样例

4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出样例:

10

b.代码实现

```
///直接拆分法
#include<iostream>
#include<cstdio>

using namespace std;

const int N=110;
int f[N];
int n,m;

int main(){
    cin >>n >>m;
    while(n--){
        int v,w,s;
        scanf("%d%d%d",&v,&w,&s);
        for(int k=1;k<=s;k++){///将这s个同样的物品单独进行选择
            for(int j=m;j>=v;j--){
                f[j]=max(f[j],f[j-v]+w);
            }
        }
    }
    
    cout <<f[m] <<endl;
    return 0;
}
```
```
///二进制拆分法
#include<iostream>
#include<cstdio>
#include<vector>

using namespace std;

const int N=11000;
int f[N];
int n,m;

class Good{
    public:
        int v,w;
};

int main(){
    cin >>n >>m;
    vector<Good> goods;///goods({int,int})
    while(n--){
        int v,w,s; 
        scanf("%d%d%d",&v,&w,&s);
         ///二进制拆分物品
        for(int k=1;k<=s;k<<=1){ ///k为2的整次幂
            s-=k; ///当前物品个数减去2的整次幂
            goods.push_back({k*v,k*w});
        }
        if(s>0) goods.push_back({s*v,s*w}); ///将剩余物品加入goods中
    }
    
    for(auto x:goods){ ///将每个物品循环
        for(int j=m;j>=x.v;j--){ ///尝试选择当前物品x
            f[j]=max(f[j],f[j-x.v]+x.w);
        }
    }
    
    cout <<f[m] <<endl;
    return 0;
}
```

4.分组背包

a.题目描述

有 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

输入样例

3 5
2
1 2
2 4
1
3 4
1
4 5

输出样例:

8

b.代码实现

#include<iostream>

using namespace std;
const int N=110;

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

int main(){
    cin >>n >>m;
    while(n--){
        int s;
        cin >>s;
        for(int i=0;i<s;i++)    cin >>v[i] >>w[i];
        for(int j=m;j>=0;j--){ ///因为每个背包只能选择一个物品,故要放在外层
            for(int k=0;k<s;k++){
                if(j>=v[k]) f[j]=max(f[j],f[j-v[k]]+w[k]);
            }
        }
    }
    
    cout <<f[m] <<endl;
    return 0;
}
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值