C++背包问题

1. 概述:何为背包

背包问题 = 一些物品 (体积, 价值, 个数)+ 一个容量有限的背包 + 求最大价值。

1.1 01背包

01背包表示物品只能选0或1个,所以叫01背包。它是所有背包的基础。

1.1.1 普通版

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 30 + 10, M = 200 + 10;

/*
递推关系式 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + c[i])
递推初值  dp[0][0] = 0

答案就是 dp[n][m]
*/

int m, n;
LL w[N], c[N];
LL dp[N][M];

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

	cout << dp[n][m] << "\n";

	return 0;
}

dp[i][j]表示当背包容量为j、选取前i件物品时背包中的最大价值。

1.1.2 优化版

由于dp数组占用空间为eq?N%5E2,N若是开到1e5就爆了,所以可以使用滚动数组优化。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 30 + 10, M = 200 + 10;



int m, n;
LL w[N], c[N];
LL dp[M];

int main() {
	cin >> m >> n;
	for (int i = 1; i <= n; i++) {
		cin >> 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]);
		}
	}
	cout << dp[m] << "\n";

	return 0;
}

 

优化后dp数组直接去掉了一维,空间优化了不少,不过注意j要倒着循环。

1.2 完全背包

完全背包表示物品可以选无限个(其实并不是无限个,而是w%5Bi%5D个)。

1.2.1 普通版

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 35, M = 210;

int m, n;
LL w[N], c[N];
LL dp[N][M];

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	cin >> m >> n;
	for (int i = 1; i <= n; i++)
		cin >> w[i] >> c[i];

	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			for (int k = 0; k <= j / w[i]; k++) {
				if (j - k * w[i] >= 0) {
					dp[i][j] = max(dp[i][j], dp[i - 1][j - k * w[i]] + k * c[i]);
				}
			}
	cout << "max=" << dp[n][m] << "\n";

	return 0;
}

1.2.2 优化版

完全背包同样可以删维。

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 35, M = 210;

int m, n;
LL w[N], c[N];
LL dp[M];

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	cin >> m >> n;
	for (int i = 1; i <= n; i++)
		cin >> w[i] >> c[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]] + c[i]);

	cout << "max=" << dp[m] << "\n";

	return 0;
}

1.3 多重背包

多重背包新增了一个变量s,表示物品有s个。所以01背包在一定程度上也算是多重背包。

1.3.1 普通版

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1010, M = 2010;

int n, m;
LL w[N], c[N], s[N];
LL dp[N][M];

int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> w[i] >> c[i] >> s[i];
	}

	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			for (int k = 0; k <= s[i]; k++) {
				if (j - k * w[i] >= 0) {
					dp[i][j] = max(dp[i][j], dp[i - 1][j - k * w[i]] + k * c[i]);
				}
			}
		}
	}

	cout << dp[n][m] << "\n";
	
	return 0;
}

1.3.2 优化版

多重背包不仅可以删维,还可以使用二进制优化。

1.3.2.1 二进制优化概念

用 1, 2, 4, 8, 16, 32, 64 可以凑出 0 ∼ 127(= 1 + 2 + ... + 64) 的所有数字。

用 1, 2, 4, 8, 16, 32, 64, 73 可以凑出 0 ∼ 200(= 1 + 2 + ... + 64 + 73) 的所有数字。

所以,s 可以用 1, 2, 4, ..., 2 k , C (0 ≤ C < 2 k+1) 凑出, 用上述思想, 便将问题转化成了 01 背包。

1.3.2.2 二进制优化代码

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1010 * 13, M = 2010;

int n, m, cnt;
LL w[N], c[N];
LL dp[M];

int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		int a, b, s;
		cin >> a >> b >> s;
		int k = 1;
		while (s - k >= 0) {
			s -= k;
			++cnt;
			w[cnt] = k * a;
			c[cnt] = k * b;
			k *= 2;
		}
		if (s) {
			++cnt;
			w[cnt] = s * a;
			c[cnt] = s * b;
		}
	}

	for (int i = 1; i <= cnt; i++) {
		for (int j = m; j >= w[i]; j--) {
			dp[j] = max(dp[j], dp[j - w[i]] + c[i]);
		}
	}
	cout << dp[m] << "\n";

	return 0;
}

1.4 混合背包

混合背包就是把上述背包组合起来(像废话)。

思路就是把完全背包和01背包转化为多重背包(数量分别为w%5Bi%5Deq?1个,上面也提到过)。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 40, M = 210;

int m, n;
LL w[N], c[N], p[N], dp[N][M];

int main() {
	cin >> m >> n;
	for (int i = 1; i <= n; i++) {
		cin >> w[i] >> c[i] >> p[i];
	}
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++) {
			if (p[i] == 0) {
				for (int k = 0; k <= j / w[i]; k++) {
					if (j - k * w[i] >= 0) {
						dp[i][j] = max(dp[i][j], dp[i - 1][j - k * w[i]] + k * c[i]);
					}
				}
			} else {
				for (int k = 0; k <= p[i]; k++) {
					if (j - k * w[i] >= 0) {
						dp[i][j] = max(dp[i][j], dp[i - 1][j - k * w[i]] + k * c[i]);
					}
				}
			}
		}
	cout << dp[n][m] << "\n";

	return 0;
}

2. 结尾

好了,文章内容就到这里了,谢谢大家~

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值