zoukankan      html  css  js  c++  java
  • 单调队列优化背包

    单调队列优化背包

    单调队列:

    • 概念:单调队列是一个双端队列,内部元素具有单调性(单调增或单调减),且保持先插入的一定在后插入的前面(也就是维护第二个值——在原序列中的编号,是递增的);

      有两个操作:

      1. 插入:从对尾插入,和目前对尾作比较,如果会破坏单调性就删除,直到找到不会破坏单调性的位置为止;
      2. 获取:直接从对头获取就OK,如果对头的序号(插入时所维护的第二个值,也就是在一个序列中的编号,再单调队列里是递增的)不满足要求就删除找下一个;
    • 用途:

      1. 用来求一段固定区间的最大值或最小值,例如滑动窗口就是道很典型的题;

      2. 优化有关固定区间长度转移的DP,例如转移方程形如:

        [dp[i]=min/max{dp[j]},(jin(i-m,i)) ]

        可以使用单调队列来优化;

      3. 优化多重背包,这也是这篇博客的主题;


    单调队列优化多重背包

    (下面都是我的个人观点,个人认为比较通俗易懂,适合像我这样的蒟蒻食用(逃))

    定义(f[i][j])为前(i)个物品使用容量(j)所能获得的最大价值;

    对于一个容量为(C)的背包,对于(d)个价值为$ w(,体积为)v$ 物品;

    那么对于(f[i][j])它只能从(f[i-1][j-k*v](kleq d and k*vleq j))转移过来;

    对于任何一个(j) ,都有(j=a*v+b) ,那么(f[i-1][j-k*v]Rightarrow f[i-1][(a-k)*v+b]) ,由此可以看出我们可以把(f[i][j])的转移由不同的余数(b)分为(v)类,这里给出一个直观的状态转移方程((b)可以把它当做一个常数):

    [f[i][a*v+b]=max{f[i-1][(a-k)*v_i+b]+k*w_i} ]

    (ps:由此往上你都可以通过画图(我是把它想像成了一个能量条来理解(滑稽)) 来感性理解,下面的就不太好感性理解了,我曾试图感性理解,但发现很容易就会想偏,所以不太推荐感性理解

    手动变形一下:(ps:这里(a)还是原来的意思,然后用(k)代替了((a-k)))

    [f[i][j]=max{f[i-1][k*v_i+b]+(a-k)*w_i}quad (kleq a and (a-k)leq d) ]

    这里可以把(a*w_i)提出来,这样就成了你平常见到的单调队列优化的方程了:

    [f[i][j]=max{f[i-1][k*v_i+b]+k*w_i}+a*w_iquad(kleq a and (a-k)leq d) ]

    你看,这样是不是就很类似上面的单调队列的用途(2)的DP方程了;

    根据这个方程,我们就可以用单调队列了,找(f[i-1][k*v_i+b]+k*w_i)在右端点为(a)长度为(d)的区间中的最大值(这里把(f[i-1][k*v_i+b]+k*w_i)看成序列中的元素,(a,k)都为序列的下标);

    这里(f[][])可以用滚动数组降一维;(这个大家应该都会)

    如果还有什么不懂的地方,可以结合代码来加深理解;

    code

    (原题链接)

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    const int N=1e4+5;
    int f[N];
    int n,m,C;
    int q[N],val[N];
    inline int read()
    {
        int f=1;
        char ch;
        while((ch=getchar())<'0'||ch>'9') if(ch=='-') f=-1;
        int res=ch-'0';
        while((ch=getchar())>='0'&&ch<='9') res=res*10+ch-'0';
        return res*f;
    }
    inline void solve(int v,int w,int d)//单调队列优化。
    {
        for(int b=0;b<v;b++)
        {
            int k=0,head=1,tail=0;
            for(int j=b;j<=C;j+=v,k++)
            {
                int tmp=f[j]-k*w;
                while(head<=tail&&tmp>val[tail]) tail--;//滚动数组,所以要先插入。
                q[++tail]=k,val[tail]=tmp;
                while(head<=tail&&k-q[head]>d) head++;
                f[j]=val[head]+k*w;
            }
        }
    }
    int main()
    {
    #ifndef ONLINE_JUDGE
        freopen("travel.in","r",stdin);
        freopen("travel.out","w",stdout);
    #endif
        n=read(),m=read(),C=read();
        for(int i=1,v,w,d;i<=n;i++)
        {
            v=read(),w=read(),d=read();
            solve(v,w,d);
        }
        for(int i=1,a,b,c;i<=m;i++)
        {
            a=read(),b=read(),c=read();
            for(int j=C;j>=0;j--)
                for(int k=0;k<=j;k++)
                    f[j]=max(f[j],f[j-k]+k*k*a+k*b+c);
        }
        printf("%d",f[C]);
        return 0;
    }
    

    对于第二种情况,直接(c^2)暴力枚举就好了,千万不要考虑二次函数的优化,数据有锅,越优化越慢,不信你去题解里找到那篇优化了的交一下,T飞。(不知道是不是我背)

    这道题还考你卡常的技巧,卡卡常就过了,还要看评测机给不给力,运气好的话,一遍就过了,如果不行就多交几遍(本人亲身经历)

    对于这道题,你还可以试试二进制拆分,不过据说会T两个点(卡卡常说不定就过了)。

  • 相关阅读:
    DOM
    JavaScript 数组的方法总结
    vuex 状态持久化插件 —— vuex-persistedstate
    移动端1px细线
    CSS多行文本并显示省略号
    Java面试题
    Git提交分支
    Redis的安装配置
    Spring IoC
    单例模式
  • 原文地址:https://www.cnblogs.com/Wednesday-zfz/p/13777579.html
Copyright © 2011-2022 走看看