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

    使用单调队列优化DP,那么必会有求i之前某个范围的极值的操作,这类DP的方程通常为:

    F[i]=min(F[j]+a[i]:j<i)

    (a[i]是与j无关的数。


    定义:队列元素保持单调递增(减),而保持的方式就是通过插队,把队尾破坏了单调性的数全部挤掉,从而使队列元素保持单调 。

    那么单调队列有什么用呢?优化DP。许多单调队列优化的DP可以使复杂度直接降维,下面就以最简单的一道题为例:

    Description

      烽火台又称烽燧,是重要的军事防御设施,一般建在险要或交通要道上。一旦有敌情发生,白天燃烧柴草,通过浓烟表达信息;夜晚燃烧干柴,以火光传递军情(晓荣的历史课讲过吼),在某两座城市之间有 n 个烽火台,每个烽火台发出信号都有一定代价。为了使情报准确地传递,在连续 m 个烽火台中至少要有一个发出信号。请计算总共最少花费多少代价,才能使敌军来袭之时,情报能在这两座城市之间准确传递。

    Input

      第一行:两个整数 N,M。其中N表示烽火台的个数, M 表示在连续 m 个烽火台中至少要有一个发出信号。接下来 N 行,每行一个数 Wi,表示第i个烽火台发出信号所需代价。

    Output

      一行,表示答案。

    Sample Input

    5 3
    1
    2
    5
    6
    2

    Sample Output

    4

    Data Constraint

    对于50%的数据,M≤N≤1,000 。 对于100%的数据,M≤N≤100,000,Wi≤100。


    分析题目,由于题目要求连续m个烽火台中至少要有一个发出信号,很容易得出DP转移方程:

    **F[i]=min(F[j]:i−m<j<i)+a[i]F[i]=min(F[j]:i−m<j<i)+a[i] **

    最直接的方法是枚举状态,对于每一个i,我们在i-m+1到i-1中寻找一个最小的F[j]进行状态转移,枚举状态的时间复杂度是O(n),寻找最小值的状态时间复杂度是O(n),因此这种方法的复杂度是O(n^2)。题目的是数据范围是n<=100000,显然超时
    那么怎么用单调队列优化呢?

    e.g.状态枚举到i,当m=4时,我们要做的就是在i-3到i-1中找到最小的F[j],那么枚举到i+1时,我们要做的就是要在i-2到i中找到最小的F[j]。

    要寻找最小值的区间向后移动了一位,也就是F[i-m+1]的值被抛弃,F[i-1]的值被加入。
    这里就可以用单调队列处理了,F[i-1]是插队的数据,F[i-1]有资格插队是因为它更优且更靠近i (又年轻又比你优秀),比它更差的数将被它取代,保留那些数据没有任何好处。而那些已经不再维护区间之外的就不必再对其进行维护,出队即可。看了代码会更加明白:

    #include<bits/stdc++.h> 
    #define rep(i,a,b) for(int i=a;i<=b;i++)
    using namespace std;
    
    int read()
    {
        int x=0,f=1;char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
        while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
        return x*f;
    }
    int n,m,ans=2147483647,head=1,tail=0;
    int q[100010],a[100010],f[100010];
    
    int main()
    {
    	n=read(),m=read();
    	
    	rep(i,1,n)a[i]=read();
    	
    	rep(i,1,n)
    	{
    		while(head<=tail && f[i-1]<=f[q[tail]])tail--;
            //注意<=要取等(虽然我们一样优,
            但是我比你年轻啊!) 
    		q[++tail]=i-1; //当F[i-1]比队尾值更优时把队
            尾值弹出,并插入 
    		
    		while(head<=tail && q[head]<i-m)head++;//不属于区间维护内的数弹出
    		f[i]=f[q[head]]+a[i];
    	}
    	
    	for(int i=n;i>n-m;i--)
    		ans=min(ans,f[i]);
    
    	printf("%d",ans);
    	return 0;
    }
    

    大菜来了

    JZOJ 1772 假期
    Description

    经过几个月辛勤的工作,FJ决定让奶牛放假。假期可以在1…N天内任意选择一段(需要连续),每一天都有一个享受指数W。但是奶牛的要求非常苛刻,假期不能短于P天,否则奶牛不能得到足够的休息;假期也不能超过Q天,否则奶牛会玩的腻烦。FJ想知道奶牛们能获得的最大享受指数。

    Input

    第一行:N,P,Q.
    第二行:N个数字,中间用一个空格隔开,每个数都在longint范围内。

    Output

    一个整数,奶牛们能获得的最大享受指数。

    Sample Input

    5 2 4
    -9 -4 -3 8 -6

    Sample Output

    5

    Data Constraint

    50% 1≤N≤10000
    100% 1≤N≤100000
    1<=p<=q<=n
    Hint
    选择第3-4天,享受指数为-3+8=5。

    怎么样?有木有很熟悉?对,mzoj 1354: 最大子序列的和原题本题(只是换了一个题目背景)


    思路:

    看到区间的问题首先肯定是想到求前缀和

    我们把[1,k]的
    和记为sum[k],可以得到sum[i] = sum[i - 1] + a[i],
    [l,r]的和即为sum[r] - sum[l - 1](这里视sum[0] =
    0)。(减法原理)

    我们假设选择的区间为[l,r]且r固定,可知
    r−B+1≤l≤r−A+1若要使[l,r]区间的值最大,则sum[l - 1]
    需最小,才可使得sum[r] - sum[l - 1]最小。
    
    当i右移一位到i+1,因为p,q为给定不变的值,对应寻
    找最小sum[l-1]的区间也右移一位
    
    #include<bits/stdc++.h> 
    #define ll long long 
    #define N 1000010
    #define rep(i,a,b) for(int i=a;i<=b;i++)
    using namespace std;
    
    int read()
    {
        int x=0,f=1;char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
        while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
        return x*f;
    }
    
    ll sum[N],q[N];
    ll n,ans=-2147483647,A,B;
     
    int main()
    {
        //freopen("input.txt","r",stdin);
        n=read(),A=read(),B=read();
        
        rep(i,1,n)
            sum[i]=sum[i-1]+read();
        
        int head=0,tail=1;
        
        rep(i,A,n)
    	{
            while(head<=tail && q[head]<i-B)//不处于维护范围内的,出队
    			head++;
    			
            while(head<=tail && sum[i-A]<=sum[q[tail]])//更优的sum[l - 1]予以插队
    			tail--;
    			
            q[++tail]=i-A;
    		ans=max(ans,sum[i]-sum[q[head]]);//更新答案
        }
        printf("%lld
    ",ans);
        return 0;
    }
    

    顶级大菜!理想的正方形


    用单调队列分别维护行与列。

    先用单调队列对每一行的值维护,并将a[][]每个区间的最大值,最小值分别存在X[][]和x[][]中。
    那么X[][]与x[][]所存储的分别是1×n的长方形内的最大值,最小值。X[i][j]存储第i行第j~j+n-1列的长方形中的
    最大值。同理,x[i][j]存储第i行第j~j+n-1列的长方形中的最小值。
    再对这两个数组的每一列上的值进行维护,将X[][]中每个区间的的最大值用Y[][]维护,将x[][]中的每个区间
    的最小值用y[][]维护。那么Y[i][j]存储X[][]中第i~i+n-1行第j列的长方形的最大值。同理y[i][j]存储x[][]中
    第i~i+n-1行第j列的长方形的最小值。

    故Y[i][j]存储的实为以a[ii+n-1][jj+n-1]中的最大,即以i,j为左上角,边长为n的正方形中的最大值。同理,
    y[i][j]存储的即以i,j为左上角,边长为n的正方形中的最小值。

    
    #include <bits/stdc++.h>
    using namespace std;
    #define rep(i,a,b) for(int i=a;i<=b;i++)
    #define N 1005
    
    int n,m,k,front,FRONT,back,BACK,ans;
    int a[N][N],q[N],Q[N];
    int x[N][N],X[N][N];
    int y[N][N],Y[N][N];
    
    int read()
    {
        int x=0,f=1;char c=getchar();
        while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
        while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
        return x*f;
    }
    
    int main()
    {
        n=read(),m=read(),k=read();
        rep(i,1,n)
        	rep(j,1,m)
        		a[i][j]=read();
    
        rep(i,1,n)
            {
                FRONT=BACK=front=back=Q[1]=q[1]=1;
                rep(j,2,m)
                    {
                        while (a[i][j]>=a[i][Q[BACK]]&&FRONT<=BACK) BACK--;
                        while (a[i][j]<=a[i][q[back]]&&front<=back) back--;
    					BACK++;back++;Q[BACK]=j;q[back]=j;
                        
    					while (j-Q[FRONT]>=k) FRONT++;
                        while (j-q[front]>=k) front++;
                        if (j>=k) X[i][j-k+1]=a[i][Q[FRONT]],x[i][j-k+1]=a[i][q[front]];
                    }
            }
            
        rep(i,1,m-k+1)
        {
            FRONT=BACK=front=back=Q[1]=q[1]=1;
            rep(j,2,n)
                {
                    while (X[j][i]>=X[Q[BACK]][i]&&FRONT<=BACK) BACK--;
                    while (x[j][i]<=x[q[back]][i]&&front<=back) back--;
                    BACK++;back++;Q[BACK]=j;q[back]=j;
                    while (j-Q[FRONT]>=k) FRONT++;
                    while (j-q[front]>=k) front++;
                    if (j>=k) Y[j-k+1][i]=X[Q[FRONT]][i],y[j-k+1][i]=x[q[front]][i];
                }
        }
            
        ans=0x3f3f3f3f;
        
        rep(i,1,n-k+1)
        	rep(j,2,m-k+1)
        		ans=min(ans,Y[i][j]-y[i][j]);
        	
        printf("%d
    ",ans);
        return 0;
    }
    
  • 相关阅读:
    反射-特性
    反射-2
    反射-1
    智能楼宇管理实用手册
    山光凝翠,川容如画——太原西山地区的历史营建与遗存
    城市逆向规划建设:基于城市生长点形态与机制的研究
    建筑快题设计50问与100例
    明清建筑二论·斗栱的起源与发展
    建筑工程计量与计价实训教程(甘肃版)
    室内设计手绘快速表现技法火星课堂
  • 原文地址:https://www.cnblogs.com/sjsjsj-minus-Si/p/11634723.html
Copyright © 2011-2022 走看看