动态规划|背包dp
1.01背包问题
2.完全背包问题
3.多重背包问题
4.多重背包问题(二进制优化)
5.混合背包问题
6.二维费用背包问题
7.分组背包问题
心有猛虎,细嗅蔷薇。你好朋友,这里是锅巴的C\C++学习笔记,常言道,不积跬步无以至千里,希望有朝一日我们积累的滴水可以击穿顽石。
背包dp的动态转移方程:
dp[j]=max(dp[j],dp[j-w[i]]+v[i])
前言:如问题给出体积和价值,我们设v和w,如果是重量和价值,我们设w和v(个人喜好),下不再解释。
01背包问题
题目:
输入描述:
输出描述:
示例1
输入
8 5
3 4
5 5
1 2
2 1
2 3
输出
10
注意:
01背包问题即选或不选的问题。
二维横轴代表背包体积(或重量),纵轴代表物品个数。
N\M | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 4 | 4 | 4 | 4 | 4 | 4 |
2 | 0 | 0 | 0 | 4 | 4 | 4 | 4 | 4 | 9 |
3 | 0 | 2 | 2 | 6 | 6 | 6 | 6 | 6 | 6 |
4 | 0 | 2 | 2 | 3 | 3 | 7 | 7 | 7 | 7 |
5 | 0 | 2 | 3 | 5 | 5 | 7 | 7 | 10 | 10 |
实践代码:
#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\M | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 4 | 4 | 4 | 8 | 8 | 8 |
2 | 0 | 0 | 0 | 4 | 4 | 4 | 8 | 8 | 8 |
3 | 0 | 2 | 4 | 6 | 8 | 10 | 12 | 14 | 16 |
4 | 0 | 2 | 4 | 6 | 8 | 10 | 12 | 14 | 16 |
5 | 0 | 2 | 4 | 6 | 8 | 10 | 12 | 14 | 16 |
实践代码:
#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];
}
心有猛虎,细嗅蔷薇。再见了朋友~