zoukankan      html  css  js  c++  java
  • 学习单调队列小结

      因为一直在听身边的人说什么单调队列/斜率优化dp/背包,(ps:我也不清楚这样称呼对不对,因为我真心是没见过这些东西)我都觉得那是神一样的东西。终于抽出时间学了一下。

      昨天在朋友一本书里面看到一句话,这里先跟大家分享一下:

      没有人会带你,人要是没有学会自立,那么将一无所能;如果过于自立,那也将一无所立.          -----柯林斯

      想想自己自学了这么长时间,却是内心的真实写照。

      一直觉得自己特别失败,这么长时间了还是一无所成,没拿什么牌,没学过多么高深的东西,现在已是迟暮之年的猥琐学子,不晓得前途在何方。而且队伍还残了。。。                                                     ------------------------------2013.3.15 更

      我终于刷完了杭电上面单调队列的题,其中有有一些简单的单调队列题目还有一些背包,动归的优化,下面的内容我都一一为大家奉上:(ps:我的刷题顺序是通过百度“hdu 单调队列”,不是由易到难)

    http://acm.hdu.edu.cn/showproblem.php?pid=3415

      题意:给定一个长度为n的环形序列,让你从中找出一个k长的子序列,使得这段序列的和是所有k长子序列中和最大的那个,输出和,并输出得到这个和时的起始位置跟终止位置。

      思路:因为还要记录起始位置跟终止位置,所以很显然队列结点还需要记录下标。我们用一个单调减队列来维护到当前下标时,前面sum的最小值,当然还需要head++使得长度控制在k的范围内。循环判断更新最大值,并记录相应下标就可以了。

    View Code
    #include <algorithm>
    #include <iostream>
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #include <queue>
    #include <map>
    
    using namespace std;
    
    const int maxn=100000+5;
    
    int a[maxn],sum[maxn<<1],head,tail,n,k,st,ed,ans;
    struct node
    {
        int val;
        int tag;
        node(int v=0,int t=0):val(v),tag(t){}
    }q[maxn<<1];
    
    void data_in()
    {
        memset(sum,0,sizeof(sum));
        memset(a,0,sizeof(a));
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            sum[i]=sum[i-1]+a[i];
        }
        for(int i=n+1;i<=n+k;i++)
            sum[i]=sum[i-1]+a[i-n];
    }
    
    int main()
    {
        int t;
        scanf("%d",&t);
        while(t--)
        {
            scanf("%d %d",&n,&k);
            data_in();
            head=1;tail=1;
            q[tail]=node(0,1);
            st=ed=1;
            ans=sum[1];
            for(int i=2;i<=n+k;i++)
            {
                while(head<=tail&&q[tail].val>sum[i-1]) tail--;
                q[++tail]=node(sum[i-1],i);
                while(head<=tail&&q[head].tag<=i-k) head++;
                int tmp=sum[i]-q[head].val;
                if(tmp>ans)
                {
                    ans=tmp;
                    st=q[head].tag;
                    ed=i;
                }
            }
            if(st>n) st-=n;
            if(ed>n) ed-=n;
            printf("%d %d %d\n",ans,st,ed);
        }
        return 0;
    }

    http://acm.hdu.edu.cn/showproblem.php?pid=3474

      题意:可以抽象成给一个只由1和-1组成的循环序列,让你求以每个点为起点且长度<=串长的子串的最小值。

      思路:贴一个好题解:http://blog.csdn.net/xymscau/article/details/6677427

    http://acm.hdu.edu.cn/showproblem.php?pid=3530

      题意:在一个序列中找一个最长的子序列,使之满足其最大值跟最小值之差val,m<=val<=k,输出该子序列的长度。

      思路:显然维护一个单调增队列跟单调减队列,如果队首元素之差满足条件的时候就更新,不满足的时候相应指针移动,并用中间变量start记录一下该序列的起始位置,更新长度即可。

      代码:

    View Code
    #include <algorithm>
    #include <iostream>
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #include <queue>
    #include <vector>
    #include <map>
    
    using namespace std;
    
    const int maxn=100000+5;
    
    int n,m,k,head1,head2,tail1,tail2,start;
    
    struct node
    {
        int val;
        int tag;
        node(int v=0,int t=0):val(v),tag(t){}
    }q[2][maxn];
    
    int main()
    {
        int ans;
        while(~scanf("%d %d %d",&n,&m,&k))
        {
            node tmp;
            head1=head2=start=1,tail1=tail2=ans=0;
            for(int i=1;i<=n;i++)
            {
                scanf("%d",&tmp.val);
                tmp.tag=i;
    
                while(head1<=tail1&&q[0][tail1].val<tmp.val) tail1--;//最大
                q[0][++tail1]=node(tmp);
    
                while(head2<=tail2&&q[1][tail2].val>tmp.val) tail2--;//最小
                q[1][++tail2]=node(tmp);
    
                while(q[0][head1].val-q[1][head2].val>k)
                {
                    start=min(q[0][head1].tag,q[1][head2].tag);
                    start==q[0][head1].tag?head1++:head2++;
                    start++;
                }
                if(q[0][head1].val-q[1][head2].val>=m&&q[0][head1].val-q[1][head2].val<=k)
                    ans=max(ans,i-start+1);
            }
            printf("%d\n",ans);
        }
        return 0;
    }

    单调队列优化:

    http://acm.hdu.edu.cn/showproblem.php?pid=1171

      又把这个题用单调队列做了一遍,代码:

    View Code
    #include <algorithm>
    #include <iostream>
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    
    using namespace std;
    
    const int maxn=55;
    
    int n,sum,val[maxn],num[maxn];
    struct Node
    {
        int tag;
        int val;
        Node(int t=0,int v=0):tag(t),val(v){};
    }que[250005];
    
    void init()
    {
        sum=0;
        for(int i=1;i<=n;i++)
        {
            scanf("%d %d",&val[i],&num[i]);
            sum+=val[i]*num[i];
        }
    }
    
    inline int max(int a,int b)
    {
        return a<b?b:a;
    }
    
    inline int min(int a,int b)
    {
        return a<b?a:b;
    }
    
    int dp[250005];
    void DP()
    {
        memset(dp,0,sizeof(dp));
        int sum1=sum/2;
        for(int i=1;i<=n;i++)
        {
            num[i]=min(num[i],sum1/val[i]);
            for(int j=0;j<val[i];j++)
            {
                int head,tail;
                head=1,tail=0;
                for(int k=0;k<=(sum1-j)/val[i];k++)
                {
                    int y=dp[k*val[i]+j]-k*val[i];
                    while(head<=tail&&que[tail].val<=y) tail--;
                    que[++tail]=Node(k,y);
                    while(que[head].tag<k-num[i]) head++;
                    dp[k*val[i]+j]=que[head].val+k*val[i];
                }
            }
        }
        printf("%d %d\n",sum-dp[sum1],dp[sum1]);
    }
    
    int main()
    {
        while(~scanf("%d",&n))
        {
            if(n<0) break;
            init();
            DP();
        }
        return 0;
    }

    还有二进制优化,请查看:http://www.cnblogs.com/RainingDays/archive/2013/05/01/3053274.html

    http://acm.hdu.edu.cn/showproblem.php?pid=4374

      具体代码请查看:http://www.cnblogs.com/RainingDays/archive/2013/05/01/3053198.html

    http://acm.hdu.edu.cn/showproblem.php?pid=3706

    代码:

    #include <algorithm>
    #include <iostream>
    #include <limits.h>
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #include <cmath>
    #include <queue>
    #include <stack>
    #include <map>
    #include <set>
    
    using namespace std;
    
    const int maxn=10000+2;
    
    struct Node
    {
        int tag;
        int val;
        Node(int t=0,int v=0):tag(t),val(v){}
    }que[maxn];
    
    int main()
    {
        int n,a,b;
        while(~scanf("%d %d %d",&n,&a,&b))
        {
            int head,tail;
            __int64 sum=1,ans=1;
            head=tail=0;
            for(int i=1;i<=n;i++)
            {
                sum=(sum%b*a%b)%b;
                while(head<tail&&que[tail-1].val>=sum) tail--;
                que[tail++]=Node(i,sum);
                while(head<tail&&que[head].tag<i-a) head++;
                ans=(ans%b*que[head].val%b)%b;
            }
            printf("%I64d\n",ans);
        }
        return 0;
    }
    View Code

    以下是单调队列斜率优化:大家可以去做做看,题目都差不多有点类似的感觉。

    http://acm.hdu.edu.cn/showproblem.php?pid=4258

    代码:

    #include <algorithm>
    #include <iostream>
    #include <limits.h>
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #include <queue>
    #include <stack>
    #include <cmath>
    #include <map>
    #include <set>
    
    using namespace std;
    
    typedef __int64 int64;
    
    const int maxn=1000000+2;
    const int INF=0x3fffffff;
    
    int64 n,c,num[maxn];
    int64 dp[maxn],que[maxn];
    
    void data_in()
    {
        for(int i=1;i<=n;i++)
            scanf("%I64d",&num[i]);
    }
    
    int64 getup(int i,int j)
    {
        return dp[i-1]+num[i]*num[i]-dp[j-1]-num[j]*num[j];
    }
    
    int64 getdown(int i,int j)
    {
        return 2*(num[i]-num[j]);
    }
    
    void DP()
    {
        int head,tail;
        head=1,tail=0;
        for(int i=0;i<=n;i++) dp[i]=(i==0)?0:INF;
        for(int i=1;i<=n;i++)
        {
            while(head<=tail&&getup(i,que[tail])*getdown(que[tail],que[tail-1])<=getup(que[tail],que[tail-1])*getdown(i,que[tail]))
                tail--;
            que[++tail]=i;
            while(head<=tail&&getup(que[head+1],que[head])<=num[i]*getdown(que[head+1],que[head])) head++;
            dp[i]=dp[que[head]-1]+c+(num[i]-num[que[head]])*(num[i]-num[que[head]]);
        }
        printf("%I64d\n",dp[n]);
    }
    
    int main()
    {
        while(scanf("%I64d %I64d",&n,&c),n+c)
        {
            data_in();
            DP();
        }
        return 0;
    }
    View Code

    http://acm.hdu.edu.cn/showproblem.php?pid=2993

    至于这个题的分析,在zy的的论文中已经很详细了,在此不再赘述。

    代码:

    #include <algorithm>
    #include <iostream>
    #include <limits.h>
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #include <string>
    #include <cmath>
    #include <queue>
    #include <stack>
    #include <map>
    #include <set>
    
    using namespace std;
    
    const int maxn=100000+5;
    
    int n,k,que[maxn];
    int sum[maxn];
    
    inline bool scan_d(int &num)
    {
        char in;
        bool isn=false;
        in=getchar();
        if(in==EOF) return false;
        while(in!='-'&&(in<'0'||in>'9')) in=getchar();
        if(in=='-')
        {
            isn=true;
            num=0;
        }
        else num=in-'0';
        while(in=getchar(),in>='0'&&in<='9')
        {
            num*=10;
            num+=in-'0';
        }
        if(isn) num-=num;
        return true;
    }
    
    void data_in()
    {
        memset(sum,0,sizeof(int)*(n+1));
        for(int i=1;i<=n;i++)
        {
            scan_d(sum[i]);
            sum[i]+=sum[i-1];
        }
    }
    
    inline double max(double a,double b)
    {
        return a<b?b:a;
    }
    
    double get(int x,int y)
    {
        return 1.0*(sum[x]-sum[y])/(x-y);
    }
    
    void DP()
    {
        int head,tail;
        double ma=0;
        head=1,tail=0;
        que[++tail]=0;
        for(int i=k;i<=n;i++)
        {
            while(head<tail&&get(i-k,que[tail])<=get(que[tail],que[tail-1])) tail--;
            que[++tail]=i-k;
            while(head<tail&&get(i,que[head+1])>=get(i,que[head])) head++;
            ma=max(ma,get(i,que[head]));
        }
        printf("%.2lf\n",ma);
    }
    
    int main()
    {
        while(~scanf("%d %d",&n,&k))
        {
            data_in();
            DP();
        }
        return 0;
    }
    View Code

    http://acm.hdu.edu.cn/showproblem.php?pid=2829

    这个题做的时候超恶心,因为发现自己一直用的单调队列的模板发现有点问题,wa了好几天。后来参考别人的写法过的。

    代码:

    #include <algorithm>
    #include <iostream>
    #include <limits.h>
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #include <cmath>
    #include <queue>
    #include <stack>
    #include <map>
    #include <set>
    
    using namespace std;
    
    const int maxn=1000+2;
    const int INF=0x7fffffff;
    
    int n,m;
    __int64 num[maxn],sumf[maxn],sum[maxn];
    double dp[maxn][maxn];
    int que[maxn];
    
    void data_in()
    {
        memset(sum,0,sizeof(sum));
        memset(sumf,0,sizeof(sumf));
        for(int i=0;i<=n+1;i++)
        {
            for(int j=0;j<=m+1;j++)
            {
                if(j==0) dp[i][j]=0;
                else dp[i][j]=INF;
            }
        }
        for(int i=1;i<=n;i++)
        {
            scanf("%I64d",&num[i]);
            sum[i]=sum[i-1]+num[i];
            sumf[i]=sumf[i-1]+num[i]*num[i];
            dp[i][0]=(sum[i]*sum[i]-sumf[i])*1.0/2;
        }
    }
    
    double getup(int x,int y,int p)
    {
        return (2*dp[x][p-1]+sum[x]*sum[x]+sumf[x])-(2*dp[y][p-1]+sum[y]*sum[y]+sumf[y]);
    }
    
    __int64 getdown(int x,int y)
    {
        return sum[x]-sum[y];
    }
    
    void DP()
    {
        for(int j=1;j<=m;j++)
        {
            int head,tail;
            head=tail=0;
            que[tail++]=0;
            for(int i=j;i<=n;i++)
            {
                while(head+1<tail&&getup(que[head+1],que[head],j)<=2*sum[i]*getdown(que[head+1],que[head])) head++;
                dp[i][j]=dp[que[head]][j-1]+((sum[i]-sum[que[head]])*(sum[i]-sum[que[head]])-(sumf[i]-sumf[que[head]]))/2;
                while(head+1<tail&&getup(i,que[tail-1],j)*getdown(que[tail-1],que[tail-2])<=getup(que[tail-1],que[tail-2],j)*getdown(i,que[tail-1]))
                    tail--;
                que[tail++]=i;
    
            }
        }
        printf("%.0lf\n",dp[n][m]);
    }
    
    int main()
    {
        while(scanf("%d %d",&n,&m),n+m)
        {
            data_in();
            DP();
        }
        return 0;
    }
    View Code

    http://acm.hdu.edu.cn/showproblem.php?pid=3507

    代码:

    #include <algorithm>
    #include <iostream>
    #include <limits.h>
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #include <cmath>
    #include <queue>
    #include <stack>
    #include <map>
    #include <set>
    
    using namespace std;
    
    const int maxn=500000+5;
    const int INF=0x3fffffff;
    
    int n,m;
    int sum[maxn];
    int dp[maxn];
    int que[maxn];
    
    void data_in()
    {
        memset(sum,0,sizeof(sum));
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&sum[i]);
            sum[i]+=sum[i-1];
        }
    }
    
    int getup(int x,int y)
    {
        return (dp[x]+sum[x]*sum[x])-(dp[y]+sum[y]*sum[y]);
    }
    
    int getdown(int x,int y)
    {
        return 2*(sum[x]-sum[y]);
    }
    
    void DP()
    {
        int head,tail;
        head=1,tail=0;
        que[++tail]=0;
        for(int i=1;i<=n;i++)
        {
            while(head<tail&&getup(que[head+1],que[head])<=sum[i]*getdown(que[head+1],que[head])) head++;
            dp[i]=dp[que[head]]+(sum[i]-sum[que[head]])*(sum[i]-sum[que[head]])+m;
    
            while(head<tail&&getup(i,que[tail])*getdown(que[tail],que[tail-1])<=getup(que[tail],que[tail-1])*getdown(i,que[tail]))
                tail--;
            que[++tail]=i;
        }
        printf("%d\n",dp[n]);
    }
    
    int main()
    {
        while(~scanf("%d %d",&n,&m))
        {
            data_in();
            DP();
        }
        return 0;
    }
    View Code

    http://acm.hdu.edu.cn/showproblem.php?pid=3480

    代码:

    #include <algorithm>
    #include <iostream>
    #include <limits.h>
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #include <cmath>
    #include <queue>
    #include <stack>
    #include <map>
    #include <set>
    
    using namespace std;
    
    const int maxn=10000+2;
    const int maxm=5000+2;
    const int INF=0x3fffffff;
    
    int n,m;
    int num[maxn],que[maxn];
    int dp[maxn][maxm];
    
    void init()
    {
        scanf("%d %d",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%d",&num[i]);
        sort(num+1,num+1+n);
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                if(j==1)
                    dp[i][1]=(num[i]-num[1])*(num[i]-num[1]);
                else if(j==i)
                    dp[i][j]=0;
                else
                    dp[i][j]=INF;
            }
        }
    }
    
    int getup(int x,int y,int p)
    {
        return dp[x][p-1]+num[x+1]*num[x+1]-(dp[y][p-1]+num[y+1]*num[y+1]);
    }
    
    int getdown(int x,int y)
    {
        return num[x+1]-num[y+1];
    }
    
    int DP()
    {
        for(int j=2;j<=m;j++)
        {
            int head,tail;
            head=tail=0;
            que[tail++]=j-1;
            for(int i=j;i<=n;i++)
            {
                while(head+1<tail&&getup(que[head+1],que[head],j)<=2*num[i]*getdown(que[head+1],que[head])) head++;
                dp[i][j]=dp[que[head]][j-1]+(num[i]-num[que[head]+1])*(num[i]-num[que[head]+1]);
                while(head+1<tail&&getup(i,que[tail-1],j)*getdown(que[tail-1],que[tail-2])<=getup(que[tail-1],que[tail-2],j)*getdown(i,que[tail-1]))
                    tail--;
                que[tail++]=i;
            }
        }
        return dp[n][m];
    }
    
    int main()
    {
        int t;
        scanf("%d",&t);
        for(int i=1;i<=t;i++)
        {
            init();
            printf("Case %d: %d\n",i,DP());
        }
        return 0;
    }
    View Code

    http://acm.hdu.edu.cn/showproblem.php?pid=3045

    代码:

    #include <algorithm>
    #include <iostream>
    #include <limits.h>
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #include <cmath>
    #include <queue>
    #include <stack>
    #include <map>
    #include <set>
    
    using namespace std;
    
    const int maxn=400000+2;
    const int INF=0x3fffffff;
    
    int n,m;
    __int64 num[maxn],sum[maxn],que[maxn];
    __int64 dp[maxn];
    
    void init()
    {
        for(int i=0;i<=n;i++)
            num[i]=sum[i]=0;
        for(int i=1;i<=n;i++)
            scanf("%I64d",&num[i]);
        sort(num+1,num+1+n);
    
        for(int i=1;i<=n;i++)
            sum[i]=sum[i-1]+num[i];
    
        for(int i=0;i<=n;i++)
            dp[i]=(i==0)?0:INF;
    }
    
    __int64 getup(int x,int y)
    {
        return dp[x]-sum[x]+x*num[x+1]-(dp[y]-sum[y]+y*num[y+1]);
    }
    
    __int64 getdown(int x,int y)
    {
        return num[x+1]-num[y+1];
    
    }
    
    void DP()
    {
        int head,tail;
        head=tail=0;
        que[tail++]=0;
        for(int i=1;i<=n;i++)
        {
            while(head+1<tail&&getup(que[head+1],que[head])<=i*getdown(que[head+1],que[head])) head++;
            dp[i]=dp[que[head]]-sum[que[head]]+sum[i]-(i-que[head])*num[que[head]+1];
            if(i+1<2*m) continue;
            while(head+1<tail&&getup(i-m+1,que[tail-1])*getdown(que[tail-1],que[tail-2])<=getup(que[tail-1],que[tail-2])*getdown(i-m+1,que[tail-1]))
                tail--;
            que[tail++]=i-m+1;
        }
        printf("%I64d\n",dp[n]);
    }
    
    int main()
    {
        while(~scanf("%d %d",&n,&m))
        {
            init();
            DP();
        }
        return 0;
    }
    View Code

      终于好久之前就想写的单调队列在我胡乱贴链接,长篇大论的废话中写完了。>。<。。

    善待每一天,努力做好自己。

    欢迎转载,注明出处。

  • 相关阅读:
    线程学习笔记(一)
    进程间通信
    管道通信操作
    在程序中执行shell命令
    进程控制(一)
    Makefile文件学习总结
    进程学习笔记
    C#不安全代码和指针
    Unity3D ShaderLab 修改渲染队列进行深度排序
    Unity3D ShaderLab 透明裁剪着色器
  • 原文地址:https://www.cnblogs.com/RainingDays/p/2961260.html
Copyright © 2011-2022 走看看