• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
jacklee404
Never Stop!
博客园    首页    新随笔    联系   管理    订阅  订阅
多重背包复习

多重背包问题

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

第 \(i\)种物品最多有 \(s_i\) 件,每件体积是 \(v_i\),价值是 \(w_i\)。

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

滑动窗口优化 \(O(n \times v)\)

\(f[i][j] = max(f[i - 1][j],f[i - 1][j - v_i] + w_i, f[i - 1][j - 2v_i] + 2w_i,...,f[i-1][j -s_iv_i] + s_iw_i)\)

\(f[i][j - v_i] = max(f[i - 1][j - v_i], f[i - 1][j - 2v_i] + w_i, ...,f[i - 1][j - (s_i + 1)v_i] + s_iw_i)\)

\(f[i][j - 2v_i] = max(f[i - 1][j - 2v_i], f[i - 1][j - 3v_i] + w_i,...,f[i - 1][j - (s_i + 2)v_i] + s_iw_i)\)

\(...\)

\(f[i][r + (s_i + 1)v_i] = max(f[i - 1][r + (s_i + 1)v_i], f[i - 1][r + (s_i)v_i] + w_i, ...,f[i - 1][r + v_i] + s_iw_i)\)

\(f[i][r + s_iv_i] = max(f[i - 1][r + s_iv_i], f[i - 1][r + (s_i - 1)v_i] + w_i, ... , f[i - 1][r] + s_iw_i)\)

\(f[i][r + (s_i - 1)v_i] = max(f[i - 1][r + (s_i - 1)v_i], f[i - 1][r + (s_i - 2)v_i] + w_i, ..., f[i -1][r] + (s_i - 1)w_i)\)

\(...\)

\(f[i][r + 2v_i] = max(f[i - 1][r + 2v_i], f[i - 1][r + v_i] + w_i, f[i - 1][r] + 2w_i)\)

\(f[i][r + v_i] = max(f[i - 1][r + v_i], f[i - 1][r] + w_i)\)

\(f[i][r] = f[i - 1][r]\)

\(r \space 表示 \space j \bmod v_i\)

​ 我们从\(r\)开始递推,考虑到\(f[i][x]\) 与\(f[i][x - v_i]\)的每个\(f[i - 1][y]\)偏移量同等的差\(w\), 因此不影响我们比较大小,这里举个例子我们在求\(f[i][r + v_i]\)时,假设\(f[i - 1][r + v_i] > f[i -1][r] + w_i\),那么递推到\(f[i][r + 2v_i]\)时, \(f[i - 1][r + v_i] + w_i > f[i - 1][r + r] + 2w_i\) 同样成立,我们只需要通过前缀的\(max\)与\(f[i - 1][r + 2v_i]\)比较即可。

​ 另外由于物品数量有限,因此对于\(x\), \(x \ge r + s_iv_i\), 它的集合中只包含取\([0, s_iv_i]\)的物品数量。

​ 例如,\(f[i - 1][r + (s_i + 1)v_i]\)的集合中并不包含\(f[i][r]\), 而是包含区间\(f[i -1][x], x \in [r + v_i, r + (s_i + 1)v_i]\),

因此我们可以用滑动窗口来处理一个窗口大小为\(s_i + 1\)个元素的窗口,来求最大值。

复杂度:\(O(n \times v)\)

滑动窗口优化Code

二维

#include <iostream>

const int N = 1e3 + 10, M = 2e4 + 10;

int q[M];
int f[N][M], v[N], w[N], s[N];
int n, m;

int main() {
	std::cin >> n >> m;

	for(int i = 1; i <= n; i ++) {
		std::cin >> v[i] >> w[i] >> s[i];
	}

	for(int i = 1; i <= n; i ++) {
		for(int r = 0; r < v[i]; r ++) {
			int hh = 0, tt = -1;
			for(int j = r; j <= m; j += v[i]) {
				if(hh <= tt && j - q[hh] > s[i] * v[i]) hh ++;
				while(hh <= tt && f[i - 1][q[tt]] + (j - q[tt]) / v[i] * w[i] <= f[i - 1][j]) tt --;
				q[++ tt] = j;
				f[i][j] = f[i - 1][q[hh]] + (j - q[hh]) / v[i] * w[i];
			}
		}
	}

	std::cout << f[n][m];
}

拷贝数组版

#include <iostream>
#include <cstring>

const int N = 1e3 + 10, M = 2e4 + 10;

int q[M];
int f[M], g[M], v[N], w[N], s[N];
int n, m;

int main() {
	std::cin >> n >> m;

	for(int i = 1; i <= n; i ++) {
		std::cin >> v[i] >> w[i] >> s[i];
	}

	for(int i = 1; i <= n; i ++) {	
		memcpy(g, f, sizeof f);
		for(int r = 0; r < v[i]; r ++) {
			int hh = 0, tt = -1;
			for(int j = r; j <= m; j += v[i]) {
				if(hh <= tt && j - q[hh] > s[i] * v[i]) hh ++;
				while(hh <= tt && g[q[tt]] + (j - q[tt]) / v[i] * w[i] <= g[j]) tt --;
				q[++ tt] = j;
				f[j] = g[q[hh]] + (j - q[hh]) / v[i] * w[i];
			}
		}
	}

	std::cout << f[m];
}

一维 (实际上是奇偶交替的,减少了拷贝数组的时间复杂度)

#include <iostream>
#include <cstring>

const int N = 1e3 + 10, M = 2e4 + 10;

int q[M];
int f[2][M], v[N], w[N], s[N];
int n, m;

int main() {
	std::cin >> n >> m;

	for(int i = 1; i <= n; i ++) {
		std::cin >> v[i] >> w[i] >> s[i];
	}

	for(int i = 1; i <= n; i ++) {	
		for(int r = 0; r < v[i]; r ++) {
			int hh = 0, tt = -1;
			for(int j = r; j <= m; j += v[i]) {
				if(hh <= tt && j - q[hh] > s[i] * v[i]) hh ++;
				while(hh <= tt && f[(i - 1) & 1][q[tt]] + (j - q[tt]) / v[i] * w[i] <= f[(i - 1) & 1][j]) tt --;
				q[++ tt] = j;
				f[i & 1][j] = f[(i - 1) & 1][q[hh]] + (j - q[hh]) / v[i] * w[i];
			}
		}
	}

	std::cout << f[n & 1][m];
}

二进制优化 \(O(n^2log(S))\)

​ 考虑一种物品\(s_i\), 我们将\(s_i\) 拆分成若干个物品组合而成, 例如\(s_i = 11\), 则其可以拆分为\(1, 2, 4, 4\) 来表示区间\([0, 11]\), 即可以理解为\((0111)_2\)可以表示\([0, 7]\), 我们给这个区间加上一个偏移量\(k = s_i - (0111)_2 = 11 - 7 = 4\), 这时能表示的区间就变成了\([0, 11]\), 我们不考虑\(4\)给以前区间出现重叠的情况,即出现多种组合表示一个数,因为这样的情况同样合法。

Code

#include <iostream>

using i64 = long long;

const int N = 1e3 + 10, M = 2e4 + 10;

int n, m;

int dp[N * 32], v[N * 32], w[N * 32], cnt;

int main() {
    std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	std::cout.tie(0);
	
	std::cin >> n >> m;

	for(int i = 1; i <= n; i ++) {
		int s1, v1, w1;
		std::cin >> v1 >> w1 >> s1;
		for(int k = 1; k <= s1; k <<= 1) {
			v[++ cnt] = k * v1, w[cnt] = k * w1;
			s1 -= k;
		}
		if(s1 > 0) {
			v[++ cnt] = s1 * v1, w[cnt] = s1 * w1;
		}
	}

	for(int i = 1; i <= cnt; i ++) {
		for(int j = m; j >= v[i]; j --) {
			dp[j] = std::max(dp[j], dp[j - v[i]] + w[i]);
		}
	}

	std::cout << dp[m];
}
posted on 2023-02-04 14:20  Jack404  阅读(26)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3