单调队列优化多重背包

参考博客:传送门

单调队列优化的多重背包时间复杂度是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;
        }
    }
}

猜你喜欢

转载自blog.csdn.net/jiangzhiyuan123/article/details/80042945