多重背包的优化 二进制/单调队列解析
来源:互联网 发布:shopee123软件怎么样 编辑:程序博客网 时间:2024/06/04 18:50
由于做题的时候老被这玩意儿卡住的我很不爽,决定写个blog来加深自己的印象以及不用到处找资料回忆。
多重背包的问题的具体描述如下:
给出一个体积为v的背包,有n个物品,每个物品可以选c[i]次,问最多能得到多大的代价。
直接做DP的复杂度是n*v*max(c[i]),这显然无法承受正常难度的数据范围。
然后我们考虑优化。
首先来看稍微简单一点的二进制优化。
很久以前打的就不要纠结是不是c艹了。
uses math;const mo=1000000007;var i,j,k,n,m:longint; dp,c,w,num:array[0..100000]of longint;procedure zpack(cost,weight,n:longint);var i:longint;begin for i:=n downto cost do dp[i]:=max(dp[i],dp[i-cost]+weight);end;procedure cpack(cost,weight,n:longint);var i:longint;begin for i:=cost to n do dp[i]:=max(dp[i],dp[i-cost]+weight);end;function multipack(n,m:longint):longint;var i,j,k:longint;begin fillchar(dp,sizeof(dp),0); for i:=1 to n do begin if num[i]*c[i]>m then cpack(c[i],w[i],m) else begin k:=1; while k<num[i] do begin zpack(k*c[i],k*w[i],m); num[i]:=num[i]-k; k:=k*2; end; zpack(num[i]*c[i],num[i]*w[i],m); end; end; exit(dp[m]);end;begin readln(n,m); for i:=1 to n do readln(c[i],w[i],num[i]); writeln(multipack(n,m));end.
具体的思想就是,如果能选的总和超过体积就做完全背包,否则就对其进行二进制拆分做01背包。然后说说二进制拆分是怎么一回事。
比如一个次数为7=111的物品,他可以被分解001 010 100三种数字。
这三种数字任意组合可以组合成不重复的,小于等于7的数字。
那么我们就是相当于对这三个数做01背包,只不过代价和价值都被累加在一起而已。
简而言之就是相当于把一个能选c[i]次的物品拆成log个只能选一次的,价值体积累加在一起的物品,所以时间复杂度是O(N*V*sigma log(c[i]))
在大部分情况下二进制拆分可以应对绝大多数的多重背包,但是也有一些题目会卡常,比如N,V<=5000就会GG,这个时候就要用到线性但是较难理解的单调队列优化。
具体来说就是:
设m[i]为合法的,i能选的最多次数。
(接下来一段比较复杂,这里直接粘贴了)
若用F[i][j]表示对容量为j的背包,处理完前i种物品后,背包内物品可达到的最大总价值,并记m[i] = min(n[i], j / v[i])。放入背包的第i种物品的数目可以是:0、1、2……,可得:F[i][j] = max { F[i - 1] [j – k * v[i] ] + k * w[i] } (0 <= k <= m[i]) ㈠如何在O(1)时间内求出F[i][j]呢?先看一个例子:取m[i] = 2, v[i] = v, w[i] = w, V > 9 * v,并假设 f(j) = F[i - 1][j],观察公式右边要求最大值的几项:j = 6*v: f(6*v)、f(5*v)+w、f(4*v)+2*w 这三个中的最大值j = 5*v: f(5*v)、f(4*v)+w、f(3*v)+2*w 这三个中的最大值j = 4*v: f(4*v)、f(3*v)+w、f(2*v)+2*w 这三个中的最大值显然,公式㈠右边求最大值的几项随j值改变而改变,但如果将j = 6*v时,每项减去6*w,j=5*v时,每项减去5*w,j=4*v时,每项减去4*w,就得到:j = 6*v: f(6*v)-6*w、f(5*v)-5*w、f(4*v)-4*w 这三个中的最大值j = 5*v: f(5*v)-5*w、f(4*v)-4*w、f(3*v)-3*w 这三个中的最大值j = 4*v: f(4*v)-4*w、f(3*v)-3*w、f(2*v)-2*w 这三个中的最大值很明显,要求最大值的那些项,有很多重复。根据这个思路,可以对原来的公式进行如下调整:假设d = v[i],a = j / d,b = j % d,即 j = a * d + b,代入公式㈠,并用k替换a - k得:F[i][j] = max { F[i - 1] [b + k * d] - k * w[i] } + a * w[i] (a – m[i] <= k <= a) ㈡对F[i - 1][y] (y= b b+d b+2d b+3d b+4d b+5d b+6d … j)F[i][j]就是求j的前面m[i] + 1个数对应的F[i - 1] [b + k * d] - k * w[i]的最大值,加上a * w[i],如果将F[i][j]前面所有的F[i - 1][b + k * d] – k * w放入到一个队列,那么,F[i][j]就是求这个队列最大长度为m[i] + 1时,队列中元素的最大值,加上a * w[i]。因而原问题可以转化为:O(1)时间内求一个队列的最大值。
代码:
#include<iostream>#include<cstdio>#include<cstdlib>#include<cstring>#include<algorithm>#define N 205#define M 20005#define inf 0x3f3f3f3fusing namespace std;int b[N],c[N],f[M],q[M],w[M];int main(){ int n,m; scanf("%d",&n); for (int i=1;i<=n;i++) scanf("%d",&b[i]); for (int i=1;i<=n;i++) scanf("%d",&c[i]); scanf("%d",&m); memset(f,inf,sizeof(f)); f[0]=0; for (int i=1;i<=n;i++) { for (int j=0;j<b[i];j++) { int head=1,tail=0; for (int k=j;k<=m;k+=b[i]) { while (head<=tail&&w[head]<k-c[i]*b[i]) head++; while (head<=tail&&f[k]-(k-w[head])/b[i]<=q[tail]) tail--; q[++tail]=f[k]; w[tail]=k; f[k]=min(f[k],q[head]+(k-w[head])/b[i]); } } } printf("%d",f[m]); return 0;}
阅读全文
0 0
- 多重背包的优化 二进制/单调队列解析
- 多重背包,二进制优化,单调队列优化
- 背包模板(01,完全,多重背包的二进制优化和单调队列优化
- poj 1276 多重背包+二进制优化+单调队列优化
- 多重背包模板--二进制优化模板&&单调队列优化模板
- 单调队列优化的多重背包
- 多重背包的单调队列优化
- 【多重背包】二进制优化 && 单调队列优化 && w == v 的特殊情况的处理
- 多重背包单调队列优化
- 单调队列优化多重背包
- 单调队列优化多重背包
- 单调队列优化多重背包
- 算法解析之感想---单调队列优化多重背包思路
- 多重背包问题的单调队列优化 转载
- hdu 2191 (多重背包的单调队列优化)
- 动态规划的单调队列优化(含多重背包)
- hdu2191多重背包单调队列优化
- Dividing(多重背包、单调队列优化dp)
- RIP协议
- hdoj 2063 过山车
- 子序列的和
- 试用了阿里云市场的验证码识别api,真的牛批,传统4位数验证码识别率超高
- 选课-二叉2
- 多重背包的优化 二进制/单调队列解析
- C++分析——多态与虚函数 virtual
- EIGRP协议
- GUID大全
- kvm-qemu 基本功能介绍(一)
- Codeforces Round #442 (Div. 2)-E-Danil and a Part-time Job(DFS序+线段树区间更新)
- LeetCode594. Longest Harmonious Subsequence
- codeforces 731A【python】
- VK Cup 2017