使用单调队列优化的 O(nm) 多重背包算法

多重背包问题与单调队列优化
本文详细解析了多重背包问题的最原始状态转移方程,并通过引入单调队列来优化状态转移过程,实现高效的求解算法。文章还提供了一个具体的代码实现案例。

我搜索了一下,找到了一篇很好的博客,讲的挺详细:链接

 

解析

多重背包的最原始的状态转移方程:

令 c[i] = min(num[i], j / v[i])

f[i][j] = max(f[i-1][j-k*v[i]] + k*w[i])     (1 <= k <= c[i])  这里的 k 是指取第 i 种物品 k 件。

如果令 a = j / v[i] , b = j % v[i] 那么 j = a * v[i] + b.

这里用 k 表示的意义改变, k 表示取第 i 种物品的件数比 a 少几件。

那么 f[i][j] = max(f[i-1][b+k*v[i]] - k*w[i]) + a*w[i]      (a-c[i] <= k <= a)

可以发现,f[i-1][b+k*v[i]] - k*w[i] 只与 k 有关,而这个 k 是一段连续的。我们要做的就是求出 f[i-1][b+k*v[i]] - k*w[i] 在 k 取可行区间内时的最大值。

这就可以使用单调队列优化。

 

代码

其中 Q1 是一个用来存储可用状态的队列, Q2 是单调队列。

//f[i][j] = max(f[i-1][b+k*v[i]] - k*w[i]) + a*w[i]   (a-c[i] <= k <= a)

for (int i = 1; i <= n; ++i) {
	Ni = Num[i]; Vi = V[i]; Wi = W[i];
	for (int j = 0; j < Vi; ++j) {
		Head1 = Tail1 = 0;
		Head2 = Tail2 = 0;
		Cnt = 0;
		for (int k = j; k <= m; k += Vi) {
			if (Tail1 - Head1 == Ni + 1) {
				if (Q2[Head2 + 1] == Q1[Head1 + 1]) ++Head2;
				++Head1;
			}
			t = f[k] - Cnt * Wi;
			Q1[++Tail1] = t;
			while (Head2 < Tail2 && Q2[Tail2] < t) --Tail2;
			Q2[++Tail2] = t;
			f[k] = Q2[Head2 + 1] + Cnt * Wi; 
			++Cnt;
		}
	}
}

 

例题:HDOJ - 1171

 

转载于:https://www.cnblogs.com/JoeFan/p/4165956.html

### 单调队列优化多重背包算法实现 对于多重背包问题,当物品数量较大时,传统的动态规划方法可能无法满足实时性的需求。为了提高效率,可以采用单调队列优化这一过程。 #### 状态定义与初始化 设`dp[i][j]`表示从前`i`种物品中选取若干件放入容量为`j`的背包可以获得的最大价值。引入辅助数组`dp1[j]`用于保存上一层的状态以便于正向更新当前层状态的同时不破坏原有数据结构[^3]。 #### 转移方程推导 原始状态下转移方程式如下: \[ dp[i][j]=\max(dp[i-1][j], \; dp[i-1][j-v_i]+w_i, \; ...,\; dp[i-1][j-k*v_i]+k*w_i)\] 其中\(v_i\)代表第`i`个物品体积,\(w_i\)为其对应的价值,而`k`则是在不超过背包剩余空间的前提下该类物品最多能放的数量。通过维护一个双端队列存储有效决策点的位置索引,在遍历过程中不断调整队首元素以保证其始终处于合法范围内并及时淘汰过期项,从而简化上述表达式至仅需考虑最近一次加入的新位置即可完成最优子结构性质下的递推计算[^2]。 ```cpp #include <deque> using namespace std; // 假设有n个不同类型的物品以及m单位重量限制 int n,m; vector<int> v(n), w(n); // 各自记录每样东西对应的体积和价格 vector<vector<int>> f(m+1); for (int i=0;i<n;++i){ deque<int> q; for(int j=0;j<=m;++j){ while(!q.empty() && j-q.front()*c>=V) q.pop_front(); if(j >= V) { int t = !q.empty()?f[q.front()][j-V]:0; f[i][j] = max(f[i-1][j],t+w); } while (!q.empty()&&f[i][j]-j/c>=f[q.back()][j-c*q.back()]) q.pop_back(); if(j>=V) q.push_back(j/V); } } ``` 此段伪代码展示了利用单调队列处理单组测试用例的过程。注意这里假设输入已经预处理好,并且所有变量都已声明适当的数据范围。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值