0
点赞
收藏
分享

微信扫一扫

6.多重背包问题3 (单调队列优化)

落花时节又逢君to 2022-02-01 阅读 44

原题链接

数据范围是0~1000,0~20000,如果使用单调队列优化的话,时间复杂度是O(nv)

单调队列优化

由来,启发

在枚举多重背包的选法的时候,会出现如图的情况,红色框内的被重复枚举,只是偏移量w不同

而dp优化的思想就是减小重复操作

如果从一个点出发,最后到不能再减v的时候,剩下的数是小于v的(模v的余数)

也就是说一个j是从他模v的余数出发的,如图r是模v的余数

 

 如图每次到一个背包容积时枚举此时所有的选法,就是在框中选一个最大值,其实可以发现是一个单调队列的过程,每次滑动窗口选择一个窗口内的最值

注意:运用单调队列优化时,要注意偏移量w

显而易见,m 一定等于 k*v + j,其中  0 <= j < v
所以,我们可以把 dp 数组分成 j 个类,每一类中的值,都是在同类之间转换得到的
也就是说,dp[k*v+j] 只依赖于 { dp[j], dp[v+j], dp[2*v+j], dp[3*v+j], ... , dp[k*v+j] }

因为我们需要的是{ dp[j], dp[v+j], dp[2*v+j], dp[3*v+j], ... , dp[k*v+j] } 中的最大值,
可以通过维护一个单调队列来得到结果。这样的话,问题就变成了 j 个单调队列的问题

从正拓扑序的角度考虑

r表示所有小于v的数  0<=r<v;
dp[r]=dp[r]
dp[r+v]=max(dp[r+v],dp[r]+w)
dp[r+2v]=max(dp[r+2v],dp[r]+2w,dp[r+v]+w)
...
dp[r+(s+1)v]=max(dp[r+(s+1)v],...dp[r+v]+sw)
...

注意到每次向右滑动窗口,窗口内所有元素的偏移量都会加上一个w

可以改写成

dp[r]    =     dp[r]
dp[r+v]  = max(dp[r], dp[r+v] - w) + w
dp[r+2v] = max(dp[r], dp[r+v] - w, dp[r+2v] - 2w) + 2w
dp[r+3v] = max(dp[r], dp[r+v] - w, dp[r+2v] - 2w, dp[r+3v] - 3w) + 3w
...
每次入队的值            dp[r+k*v]-k*w
每次计算窗口内的值      dp[j]=dp[q[hh]]+(j-q[hh])/v*w
j表示当前窗口的最后一个元素
每次计算时 例如 dp[r+3v] 
在j==r+3v时,dp[r+3v]
在j==r+4v时,dp[r+4v]+w
在j==r+5v时,dp[r+5v]+2w ...

操作 

  • 放入队列的时候比较当前的尾端+当前偏移量    g[q[tt]]-(q[tt]-r)/vi*wi<=g[j]-(j-r)/vi*wi
  • 在队列中存放的是dp数组值                               dp[hh~tt]
  • 出队计算的时候选择的是前面最大的值+当前偏移量       dp[j]=g[q[hh]]+(j-q[hh])/vi*wi;

单调队列问题,最重要的两点

  • 维护队列元素的个数,如果不能继续入队,弹出队头元素
  • 维护队列的单调性,即:尾值 >= dp[j + k*v] - k*w,这里维护最大值,就是单调递减

本题中,队列中元素的个数应该为 s+1 个,即 0 -- s 个物品 i

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=2e4+10;
int dp[N],g[N],q[N];
int w[N],v[N],s[N];

int main()
{
    int n,V;
    cin>>n>>V;
    for(int i=1;i<=n;i++)
        cin>>v[i]>>w[i]>>s[i];
    for(int i=1;i<=n;i++)   //选择i个物品
    {   
        memcpy(g,dp,sizeof dp); //在枚举过程中要求保持前面的值
        //dp数组可能被改变,所以拷贝一下,开二维数组可以不用这个步骤
        int vi=v[i],si=s[i],wi=w[i];
        for(int r=0;r<v[i];r++) //枚举余数
        {
            int hh=0,tt=-1;
            for(int j=r;j<=V;j+=vi)//枚举所有选法(包括不选)
            {
                if(hh<=tt&&q[hh]<j-si*vi) hh++;
                while(hh<=tt&&g[q[tt]]-(q[tt]-r)/vi*wi<=g[j]-(j-r)/vi*wi)
                    tt--;
                q[++tt]=j;
                dp[j]=g[q[hh]]+(j-q[hh])/vi*wi;
            }
        }   
    }
    cout<<dp[V];
}
举报

相关推荐

0 条评论