单调队列优化多重背包

单调队列优化多重背包

 背包问题:

   01,完全比较简单,套个板子就好,需要注意的是:

   const int maxn = 2e4+11;//由于maxn是由max(n,m)决定的,当背包的体积过大时,我们就不能再使用体积来定义状态了   

    来一道例题:https://ac.nowcoder.com/acm/problem/15948  【  题目的V 巨大,我们不能再定义dp[ i ] 为体积为i的状态了,观察到n很小,限制一下v的范围,然后记忆化dfs即可】

    多重背包优化:

   ① 二进制拆分,容易运用

  ② 单调队列,十分快,时间是o(nv) 少了logk,但是很难理解(  

int _ML()
{
int pre = 0,now = 1,ans = 0,k,V,VAL;
for(int i = 1;i <= n;i++)
{
k = 1;
while(c[i]>0)
{
if(c[i]<k)k = c[i];
V = v[i]*k,VAL = val[i]*k;

for(int j = 0;j <= m;ans = max(dp[now][j],ans),j++)
if(j<V)dp[now][j] = dp[pre][j];//继承状态不能少
else dp[now][j] = max(dp[pre][j-V]+VAL,dp[pre][j]);
swap(pre,now);//每计算完一次转换状态

c[i] -= k;//减去系数
k *= 2;
}
}
return ans;
}
拆分板子
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <cmath>
#include <set>
#include <map>
#define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#define ll long long
#define ls (ro<<1)
#define rs (ro<<1|1)
#define me(a,b) memset(a,(b),sizeof a)
using namespace std;
const int maxn = 1e5+11;
//maxn 取决于 max(n,V);
int t,n,m,V,val,v,c,dp[maxn],k,ans;

struct PACK{int id,val;};
PACK q[maxn];
int head,tail;

int main()
{
    while(~scanf("%d%d",&n,&V))
    {
        me(dp,0);ans = 0;
        
        for(int i = 1;i <= n;i++)
        {
            scanf("%d%d%d",&val,&v,&c);
            
            //int d;  余数
            //int k;  倍数
            //int c;  min(c[i],m/v[i]) 能使用的个数
            c = min(c,V/v);
            for(int d = 0;d < v;d++)
            {
                k = (V-d)/v;
                head = 0,tail = 0;//clear队列
                for(int j = 0;j <= k;j++)
                {
                    //删除队列尾部小于这个数的数,然后再插进去维护单调性(基操)
                while(head<tail&&dp[d+j*v]-j*val >= q[tail-1].val)//这里的 >= 控制严格单调性
                        tail--;
                    q[tail].id = j;//记录index
                    q[tail++].val = dp[d+j*v] - j*val;
                    
                    //如果head的数超出了搜索的范围,pop it out
                    while(head<tail&&q[head].id < j - c)
                        head++;
                    //每一个dp[d+j*v]来自于前面的最大值/0 + 队列最大值 + j * val
                    dp[d+j*v] = max(dp[d+j*v],q[head].val+j*val);
                }
            }
        }
        for(int i = 1;i <= V;i++)ans = max(dp[i],ans);
        printf("%d\n",ans);
    }
}
单调队列板子  

    关于单调队列,我目前只发现运用于最大最小值的状态转移...     

    单调队列多重背包板子题

    讲解1   讲解2   讲解3    

   我的理解:(图片转载)

  为甚么要取d余数,并且遍历余数呢?

讲解中说得十分清楚,因为只有d相同的数,才会有联系(后面的继承前面的),

 所以我们按 d 分组,并且一个个遍历。

  为什么要剪掉  j * val 之后再放入队列呢?

  首先看:pre状态由{ 1,1 ,1} { 1,2,1 } { 1,3,1}{ 1,4,1} { 价值,体积,个数}

  这几件物品转移而来,其实看一下也可以看出来

  那么我们现在加入{ 2,3,2}的物品

pre: 1 2 3 4 4 4 4
now: 1 2 3 4 5 6 6

①我们d = 0,j = 0 -> k 时,是蓝色部分,同时最多可用的物品数量是 c = min( c , V/v)  = 2,

② 考虑到:放入队列的数是 1 , (4-2),(4-2*2)即{  1 , 2, 0  }//注意是从pre转到now

③最后一个点显然从第4个点加上一个物品就好,但是我们计算的时候是  :

  dp[ d + j*v] = max(dp[d + j * v],q[head].val + j * val); 也就是最大值2 +2*val

④可以看出:减去 j * val 是因为容斥定理  是不是恍然大悟呢


懂了的话,来一道题目练练手:取最小值的多重背包 原题-->【POJ2005】

我们维护的单调队列是区间最小值,需要注意的是,每一个DP状态设置为INF,dp[0] = 0;

注意每一个变量代表的含义,不要混乱了

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define me(a,b) memset(a,(b),sizeof a)
using namespace std;

const int maxn = 5e5+11,inf = 0x3f3f3f3f;
int dp[maxn],val[maxn],c[maxn],n,m,V;

struct PACK{int id,val;};
PACK q[maxn];
int head,tail;

int main()
{
    while(~scanf("%d",&n))
    {
        me(val,0),me(c,0),me(dp,inf);
        for(int i = 1;i <= n;i++)scanf("%d",&val[i]);
        for(int i = 1;i <= n;i++)scanf("%d",&c[i]);
        scanf("%d",&V);
        dp[0]=0;
        for(int i = 1;i <= n;i++)
        {
            int d = val[i],tot = min(c[i],V/val[i]);
            for(int j = 0;j < d;j++)//余数
            {
                head = tail = 0;
                q[head].val = -inf;//设置为最小值,防止出现负值
                int k = (V-j)/val[i];
                //k倍数
                for(int t = 0;t <= k;t++)
                {
                    while(head<tail&&dp[j+t*val[i]]-t<=q[tail-1].val)tail--;
                    q[tail].id = t;
                    q[tail++].val = dp[j+t*val[i]]-t;
                    while(head<tail&&q[head].id < t - tot)head++;
                    dp[j+t*val[i]] = min(dp[j+t*val[i]],q[head].val+t);
                    //调试代码
                    //for(int i = 0;i <= V;i++)
                        //cout<<dp[i]<<" ";
                    //cout<<endl;
                }
            }
        }

        if(dp[V]!=inf)printf("%d\n",dp[V]);
        else puts("-1");
    }
    return 0;
}
View Code

 

posted @ 2021-01-18 21:49  PigeonG  阅读(145)  评论(0)    收藏  举报