参考博客:传送门
单调队列优化的多重背包时间复杂度是0(N*V)
对单调队列不是很了解的可以看下我的这篇博客:传送门
想必大家对多重背包的松弛:dp[i][j] = max(dp[i-1][j],dp[i-1][j-k*c[i]]+k*v[i])
已经很熟悉了,这里就不多讲了。
如何在O(1)时间内求出dp[i][j]呢?
先看一个例子:取m[i] = 2, c[i] = c, v[i] = v。
并假设 f(j) = dp[i - 1][j],观察公式右边要求最大值的几项:
j = 6*c: 求f(6*c)、f(5*c)+v、f(4*c)+2*v 这三个中的最大值
j = 5*c: 求f(5*c)、f(4*c)+v、f(3*c)+2*v 这三个中的最大值
j = 4*c: 求f(4*c)、f(3*c)+v、f(2*c)+2*v 这三个中的最大值
显然,右边求最大值的几项随j值改变而改变,但如果将j = 6*c时,每项减去6*v,j=5*c时,每项减去5*v,j=4*v时,每项减去4*v,就得到:
j = 6*c: f(6*c)-6*v、f(5*c)-5*v、f(4*c)-4*v 这三个中的最大值
j = 5*c: f(5*c)-5*v、f(4*c)-4*v、f(3*c)-3*v 这三个中的最大值
j = 4*c: f(4*c)-4*v、f(3*c)-3*v、f(2*c)-2*v 这三个中的最大值
很明显,要求最大值的那些项,有很多重复。
即我们求出j = 4*c的最大值后,我们要想求j = 5*c,这时我们只要再比较下f(5*c)-5*v就可以了(单调队列的思想)
根据这个思路,可以对原来的公式进行如下调整:
假设d = c[i],a = j / d,b = j % d,即 j = a * d + b,并用t替换a - k得:
dp[i][j] = max { dp[i - 1] [b + t * d] - t * v[i] } + a * w[i] (a – m[i] <= k <= a), 即类似于求区间[a-m[i],a]的最大值。
对dp[i - 1][y] (y= b b+d b+2d b+3d b+4d b+5d b+6d … j)
dp[i][j]就是求j的前面m[i] + 1个数对应的dp[i - 1] [b +t * d] - t* v[i]的最大值,最后加上a * w[i]。
好吧其实大部分是参考博客盗来的。
附上自己写的代码:
const int N = 110;
const int M = 250010;
struct Node{
int id,val;
Node(){}
Node(int _id,int _v){
id = _id; val = _v;
}
}q[M];
int dp[M],V; //V是所有商品的总价值
int cnt[N],val[N];
//num:某类物品数量,cost:某类物品花费,value:某类物品价值
void MultiPack(int num,int cost,int value)
{
for(int j = 0; j < cost; ++j){
int head = 1,tail = 0;
for(int k = j,i = 0; k <= V/2; k += cost, ++i){
int r = dp[k]-i*value;
while(head <= tail && r >= q[tail].val) tail--;
q[++tail] = Node(i,r);
while(q[head].id < i-num) head++;
dp[k] = q[head].val+i*value;
}
}
}