蒟蒻的背包专栏 (01背包,完全背包,多重背包)(着重阐述笔者的理解而不是推导过程和优化过程~)

废话一堆

(本篇着重阐述的是笔者不熟的,目的也是为了帮助笔者理清知识,所以有很多不够详细的地方,望多多担待)(总而言之就是本篇是笔者写给自己看的
背包应该是今年疫情前学的吧,笔者在复习的时候才发现自己的背包学得有多么辣鸡。所以笔者只能比学得好的大佬多用时间好好的整理一下自己的背包(欢迎大佬提意见,但是求轻喷)

前言

度娘说:背包问题(Knapsack problem)是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高

辣鸡笔者的话转换一下就是:在有限容量时面对不同情况,能获得的最高价值。
那么在这些不同的情况中,最基础的就是01背包,完全背包,以及多重背包。

01背包

gm的ppt说:有N件物品和一个容量为V的背包。放入第i件物品耗费的体积是vi,得到的价值是Pi。
求解:将哪些物品装入背包可使价值总和最大?

总而言之:每一种物品有且只有一件,故只有选与不选两种,所以称作01背包。

一维数组优化

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 35;
int m, n;
int w[maxn], c[maxn], dp[maxn];
int main() {
	scanf("%d %d", &m, &n);
	for(int i = 1; i <= n; i ++){
		scanf("%d %d", &w[i], &c[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]] + c[i]);
		}
	}
	printf("%d", dp[m]);
	return 0;
}

经典例题(版题)有:采药,开心的金明,Subset Sums集合…

完全背包

完全背包与01背包不同的是完全背包中的物品有无限件

\\朴素版
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 1e5;
int m, n;
int a[maxn], b[maxn];
long long dp[maxn];
int main() {
	scanf("%d %d", &m, &n);
	for(int i = 1; i <= n; i ++){
		scanf("%d %d", &a[i], &b[i]);
	}
	for(int i = 1; i <= n; i ++){
		for(int j = m; j >= a[i]; j --){
			for(int k = 1; k <= j / a[i]; k ++){
				dp[j] = max(dp[j], dp[j - k * a[i]] + k * b[i]);
			}
		}
	}
	printf("%d", dp[m]);
	return 0;
}

从这上面,我们就可以发现朴素算法的弱点了,它的时间复杂度是 O ( n 3 ) O( n^3 ) O(n3)一不小心就超时

如果我们把它转化为01背包来思考呢?一件东西如果我们要取它,就必须保证它取了至少一件,因此我们转化为先放一件进背包然后再考虑下一个是否取的问题。

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 1e5;
int m, n;
int a[maxn], b[maxn];
long long dp[maxn];
int main() {
	scanf("%d %d", &m, &n);
	for(int i = 1; i <= n; i ++){
		scanf("%d %d", &a[i], &b[i]);
	}
	for(int i = 1; i <= n; i ++){
		for(int j = a[i]; j <= m; j ++){
			dp[j] = max(dp[j], dp[j - a[i]] + b[i]);
		}
	}
	printf("%lld", dp[m]);
	return 0;
} 
多重背包

多重背包其实与01背包没有本质性的区别,只是完全背包每一件东西都是有限件但可以不止一件。

//依旧是朴素
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 20;
int n, m;
int a[maxn], b[maxn], num[maxn];
long long dp[maxn * maxn];
int main() {
	scanf("%d %d", &m, &n);
	for(int i = 1; i <= n; i ++){
		scanf("%d %d %d", &a[i], &b[i], &num[i]);
	}
	for(int i = 1; i <= n; i ++){
		for(int j = m; j >= 1; j --){
			for(int k = 1; k <= num[i] && k <= j / a[i]; k ++){
				dp[j] = max(dp[j], dp[j - a[i] * k] + k * b[i]);
			}
		}
	}
	printf("%lld", dp[m]);
	return 0;
}

朴素依旧时间复杂度极高
这是我们就要引出大名鼎鼎的二进制优化啦(金色传说

又是gm的ppt说:
二进制优化思想:每类物品都有 n u m i num_i numi件,而01背包DP的思想是每件物品取与不取的线性递推,也就是说每类物品可以取0,1,2,……, n u m i num_i numi 件。而我们使用二进制把原有的 n u m i num_i numi件相同物品合并成体积和价值都扩大系数倍的1,2,4,8,……, numi -2k+1 件不同的物品,同样可以通过取与不取组合出0,1,2,……, n u m i num_i numi件相同物品的效果,且组合之后的每件不同物品都只能取到1次。这样处理之后,时间复杂度变为 O ( l o g 2 ∑ n u m i ∗ V ) O(log2∑numi*V) O(log2numiV) , 1<=i<=n

我们知道,所有自然数都可以用二进制来表示,因此每一件东西的数量我们都转化成可以由0,1,2,4…组成的。

// 二进制优化版
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 205;

int m, n;
int v[maxn], p[maxn], dp[maxn];
int cnt;
int main() {
	scanf("%d %d", &m, &n);
	for(int i = 1; i <= n; i ++){
		int one_v, one_p, one_num;
		scanf("%d %d %d", &one_v, &one_p, &one_num);
		for(int j = 1; j <= one_num; j <<= 1){
			cnt ++;
			v[cnt] = j * one_v;
			p[cnt] = j * one_p;
			one_num -= j;
		}
		if(one_num){
			cnt ++;
			v[cnt] = one_num * one_v;
			p[cnt] = one_num * one_p;
		}
	}
	for(int i = 1; i <= cnt; i ++){
		for(int j = m; j >= v[i]; j --){
			dp[j] = max(dp[j], dp[j - v[i]] + p[i]);
		}
	}
	printf("%d", dp[m]);
} 
总结

01,完全,多重背包都是最基础,必须要掌握的背包。同时最重要的还是01背包,因为任何的背包都可以转换为01背包解决。

笔者花了很长很长的时间终于写完了本篇,大概对于这三种背包的理解也更深一步了吧~

同时背包一共有九讲,对于混合背包,二维费用背包就下次再写吧!

下次一定

特备鸣谢

郭老师和他()的PPT,
大佬关于完全背包的博客

©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页