背包问题(题目来自Acwing)

327 阅读5分钟

lc没有背包问题,只有水壶跟鲜花的问题。就来acwing讨生活啦。

背包九讲入门款:

2. 01背包问题

首先呢,根据y神讲的顺序,俺也先来给一下:

题目:

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

第 ii 件物品的体积是 vivi,价值是 wiwi。

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

输出最大价值。

输入格式

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

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

输出格式

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

数据范围

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

输入样例

4 5
1 2
2 4
3 4
4 5

输出样例:

8

解析(朴素解法\二维空间解法)

先大概说一下原理,背包问题的朴素解法的子问题就是,我们关心的是第i件物品是否放入背包的问题。我们只有选和不选两种选择:

不选 就是 dp[i][j]=dp[i-1][j],也就是在空间大小为j的条件下,不加第i个物品的情况。 选 就是dp[i][j] = dp[i-1][j-v[i]]+w[i],也就是这个位置放的是不算物品i的占用空间v[i]的时候,那个空间所产生的价值加上i的价值w[i]的价值。

选和不选取最大值就好啦。 such as:

**这里参考的是《算法图解》

手机:重1磅,价值1500元

吉他:重1磅,价值1500元

笔记本电脑:重3磅,价值4000元

现在有一个最多装4磅的袋子看看怎么装最大。

1磅2磅3磅4磅
手机
吉他
笔记本电脑

第一行只考虑手机,所以我们能拿到的最高价值就是1500所以全填1500。

1磅2磅3磅4磅
手机1500150015001500
吉他
笔记本电脑

第二行有吉他了,我们考虑两种情况,不选吉他,看同列上面一行的价值,吉他行1磅列,那么不选吉他在一磅的情况下价值是1500,选吉他,看1磅-1磅这表格里没有这个,写代码的时候,咱们是从1开始的,这些是代码的问题,就不要深究了,0+1500 = 1500,所以吉他一磅这里最高的价值是1500,然后看吉他行2磅列,不选吉他在一磅的情况下价值是1500,2磅-1磅是1磅,故看上一行,一磅列的值,加上吉他的价值,也就是3000,就是价值了。剩下的同理。也就是:

1磅2磅3磅4磅
手机1500150015001500
吉他1500300030003000
笔记本电脑

最后一行,也就是笔记本电脑行,因为笔记本电脑为3磅,所以1磅,2磅的值都为不选笔记本电脑的值,也就是1500和3000,看第三列,不选笔记本电脑为3000,如果选了,那么看3磅-3磅 = 0,所以此时价值为0,此时的价值为4000+0,也就是4000,4磅列的话,同理不选笔记本电脑为3000,如果选了,那么看4磅-3磅 = 1磅,上一行1磅的价值的为1500,加上4000,也就是5500。

1磅2磅3磅4磅
手机1500150015001500
吉他1500300030003000
笔记本电脑1500300040005500

这里最大价值也就是5500啦

代码:

#include<iostream>
using namespace std;
#define N 1001 //根据题目确认范围
int v[N],w[N];//v是物品体积,w是物品价值
int dp[N][N];//这就代表咱们之前画的小格子啦,在堆上默认所有值为0。dp[i][j] , i = 0也就是什么东西也不放,默认这一行都是等于0的呢。
int m,n;//n代表物品数量,m代表背包容积。
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++){
        dp[i][j] = dp[i-1][j]; //不选第i种物品的情况下的结果
        if(j >= v[i]) dp[i][j] = max(dp[i][j],dp[i-1][j-v[i]]+w[i]);//选第i种物品的情况下的结果dp[i-1][j-v[i]]+w[i],与不选的情况做个比较
    }
    cout<<dp[n][m];
    
}

解析2-滚动数组优化

这是一个优化啦,因为我们可以看到,第i行需要进行操作的时候,只需要看第i-1行的数据就好啦,那我们可不可以只用一个一维数组来进行dp呢? 比如:

1磅2磅3磅4磅
手机1500150015001500
吉他
笔记本电脑

此时数组为[1500,1500,1500,1500] 那么吉他行,先看4磅的时候放不放吉他,4磅的时候,看手机4磅的数与手机(4磅-1磅)3磅然后加上吉他的价值的内容,这样只需要更新吉他第四个数。然后看吉他3磅,这时候根本不用管手机4磅。所以理论上说,完全可以用一个数组来完成这个问题。

#include<iostream>

using namespace std;

#define N 1001 //根据题目确认范围
int v[N],w[N];//v是物品体积,w是物品价值
int dp[N];//这就代表咱们之前画的小格子啦,在堆上默认所有值为0。dp[i][j] , i = 0也就是什么东西也不放,默认这一行都是等于0的呢。
int m,n;//n代表物品数量,m代表背包容积。
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--){
        dp[j] = max(dp[j],dp[j-v[i]] + w[i]);
    }
    
    cout<<dp[m];
    return 0;
}

在输入输出上下手,继续优化算法

#include<iostream>

using namespace std;

#define N 1001 //根据题目确认范围
int v,w;//v是物品体积,w是物品价值
int dp[N];//这就代表咱们之前画的小格子啦,在堆上默认所有值为0。dp[i][j] , i = 0也就是什么东西也不放,默认这一行都是等于0的呢。
int m,n;//n代表物品数量,m代表背包容积。
int main(){
    
    cin>>n>>m;

    for(int i = 1;i <= n;i++){
    cin>>v>>w;
    for(int j = m;j>=v;j--){
        dp[j] = max(dp[j],dp[j-v] + w);
    }
    }
    cout<<dp[m];
    return 0;
}

完全背包问题

多重背包问题 I

多重背包问题 I

多重背包问题 II

多重背包问题 III

混合背包问题

二维费用的背包问题

分组背包问题

有依赖的背包问题

背包问题求方案数

背包问题求具体方案