zoukankan      html  css  js  c++  java
  • 单调队列DP

    单调队列dp

    其实单调队列就是一种队列内的元素有单调性的队列,因为其单调性所以经常会被用来维护区间最值或者降低DP的维数已达到降维来减少空间及时间的目的。

    单调队列与普通队列不一样的地方就在于单调队列是双端队列,既可以从队首出队,也可以从队尾出队。

    队列q[ ]存的是下标、 单调队列中元素大小单调 ; 队首元素必定是最值

      单调队列的一般应用:

        1.维护区间最值    2.优化DP

    单调队列优化时间复杂度O(n)

    例1:滑动窗口

    有一个长为 n 的序列 a,以及一个大小为 k 的窗口。现在这个从左边开始向右滑动,每次滑动一个单位,求出每次滑动后窗口中的最大值和最小值。

    1≤kn≤10^6

    !!!注意:数组开大点

    #include<iostream> 
    #include<cstdio> 
    using namespace std;
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    int n,k,a[5000005];
    int q1[5000005],q2[5000005];
    void minn(){
    	int h=1,t=0;
    	for(int i=1;i<=n;i++){
    		while(h<=t&&q1[h]<=i-k)h++;
    		while(h<=t&&a[i]<a[q1[t]])t--;
    		q1[++t]=i;
    		if(i>=k)printf("%d ",a[q1[h]]);
    	}
    }
    void maxx(){
    	int h=1,t=0;
    	for(int i=1;i<=n;i++){
    		while(h<=t&&q2[h]<=i-k)h++;
    		while(h<=t&&a[i]>a[q2[t]])t--;
    		q2[++t]=i;
    		if(i>=k)printf("%d ",a[q2[h]]);
    	}	
    }
    int main(){
    	n=read();k=read();
    	for(int i=1;i<=n;i++)a[i]=read();
    	minn();puts("");
    	maxx();puts("");
    	return 0;
    } 
    

    同样的裸题 https://www.luogu.com.cn/problem/P1440

    只是把输出提前了...

    #include<iostream> 
    #include<cstdio> 
    using namespace std;
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    int n,k,a[5000005];
    int q[5000005];
    int main(){
    	n=read();k=read();
    	for(int i=1;i<=n;i++)a[i]=read();
    	int h=1,t=0;
    	for(int i=1;i<=n;i++){
    		printf("%d
    ",a[q[h]]);
    		while(h<=t&&q[h]<=i-k)h++;
    		while(h<=t&&a[i]<a[q[t]])t--;
    		q[++t]=i;
    	}
    	return 0;
    } 
    

    例2:琪露诺

    她只移动到区间[i+l,i+r]中的任意一格 ,且只能向右移动

    从n+1-r到n都可以跳出

    #include<iostream> 
    #include<cstdio> 
    #include<cstring> 
    using namespace std;
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    const int N=5000005;
    int n,L,R,a[N],dp[N],q[N],ans=-0x3f3f3f3f;
    int main(){
    	n=read();L=read();R=read();
    	for(int i=0;i<=n;i++)a[i]=read();
    	int h=1,t=0,pos=0;
    	memset(dp,-0x3f,sizeof(dp));dp[0]=0;
    	for(int i=L;i<=n;i++){
    		while(h<=t&&dp[pos]>dp[q[t]])t--;
    		q[++t]=pos;
    		while(q[h]+R<i)h++;
    		dp[i]=dp[q[h]]+a[i];
    		pos++;
    	}
    	for(int i=n-R+1;i<=n;i++)
    		ans=max(ans,dp[i]);
    	printf("%d
    ",ans);
    	return 0;
    } 
    

    例3:修剪草坪

    FJ有N(1 <= N <= 100,000)只排成一排的奶牛,编号为1...N。每只奶牛的效率是不同的,奶牛i的效率为E_i(0 <= E_i <= 1,000,000,000)。

    靠近的奶牛们很熟悉,因此,如果FJ安排超过K只连续的奶牛,那么,这些奶牛就会罢工开派对:)。因此,现在FJ需要你的帮助,计算FJ可以得到的最大效率,并且该方案中
    没有连续的超过K只奶牛。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    #define LL long long
    #define N  100005
    int n,k,q[N],e[N];
    LL ans=0,mn=99999999999999LL,f[N];
    int main(){
    	scanf("%d%d",&n,&k);
    	for(int i=1;i<=n;i++)
    		scanf("%d",&e[i]),ans+=e[i];
    	int h=0,t=0;
    	for(int i=1;i<=n;i++){
    		f[i]=e[i]+f[q[h]];
    		while(h<=t&&f[i]<f[q[t]])t--;
    		q[++t]=i;
    		while(q[h]<i-k)h++;
    	}
    	for(int i=n-k;i<=n;i++)
    		mn=min(mn,f[i]);
    	printf("%lld",ans-mn);
    	return 0;
    } 
    

    例4:旅行问题

    Solution

    将环给拆成一条链跑.
    正反跑两次,维护路上前缀和,内容是每一个加油站的贡献,也就是p-d.如果路上最小的前缀和大于等于sum[i] 了,那么这种方式就可行,正反跑两遍就行.

    #include<cstdio> 
    #include<cstring> 
    #include<iostream> 
    using namespace std;
    #define LL long long 
    #define N 1000010
    int n,q[N],h=1,t,p[N],d[N],ans[N<<1];
    LL a[N<<1],sum[N<<1];
    int main() {
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++)
    		scanf("%d%d",&p[i],&d[i]);
    	d[0]=d[n];
    	for(int i=1;i<=n;i++)
    		a[i]=a[i+n]=p[i]-d[i];
    	for(int i=1;i<2*n;i++)
    		sum[i]=sum[i-1]+a[i];
    	for(int i=1;i<2*n;i++) {
    		while(h<=t&&q[h]<=i-n)h++;
    		while(h<=t&&sum[q[t]]>=sum[i])t--;
    		q[++t]=i;
    		if(i>=n&&sum[q[h]]>=sum[i-n])ans[i-n+1]=1;
    	}
    	for(int i=1;i<=n;i++)
    		a[i]=a[i+n]=p[n-i+1]-d[n-i];
    	for(int i=1;i<2*n;i++)
    		sum[i]=sum[i-1]+a[i];
    	h=1,t=0;
    	for(int i=1;i<2*n;i++) {
    		while(h<=t&&q[h]<=i-n)h++;
    		while(h<=t&&sum[q[t]]>=sum[i])t--;
    		q[++t]=i;
    		if(i>=n&&sum[q[h]]>=sum[i-n])ans[2*n-i]=1;
    	}	 
    	for(int i=1;i<=n;i++){
    		if(ans[i])puts("TAK");
    		else puts("NIE");
    	}
    	return 0;
    }
    

    例5:跳房子

    Solution

    如果g个金币能满足条件,那么g+1个肯定也能,题目要求最大值最小,显然二分

    然后暴力的做法(60pts)伪代码——

    bool check(int g){
    	for(i:1~n)f[i]=-inf;
        f[0]=0;
        int l=min(d-g,1),r=d+g;
        for(i:1~n)
            for(j:i-1~1){
                if(x[i]-x[j]<l)continue;
                if(x[i]-x[j]>r)break;
                f[i]=max(f[i],f[j]+w[i]);
                if(f[i]>=k)return 1;
            }
        return 0;
    }
    

    然后 注意到 i 的区间( l,r )满足单调性,所以用单调队列优化

    代码很多小细节

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    
    #define LL long long
    const int N =500005;
    const int inf=0x3f3f3f3f;
    
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    	while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    	return x*f;
    }
    int n,d,k,s[N],q[N],x[N];
    LL mx,f[N];
    
    bool check(int g) {
    	for(int i=1;i<N;i++)f[i]=-inf,q[i]=0;
    	f[0]=0;
    	int l=1,r=0,j=0;
    	int R=d+g,L=max(1,d-g);//*1
    	for(int i=1;i<=n;i++) {
    		while(x[i]-x[j]>=L&&j<i) {
    			if(f[j]!=-inf){
    				while(l<=r&&f[q[r]]<=f[j])r--;
    				q[++r]=j;
    			}
    			j++;
    		}
    		while(l<=r&&x[i]-x[q[l]]>R)l++; //*2
    		if(l<=r) f[i]=f[q[l]]+s[i];
    	}
    	LL res=-inf;
    	for(int i=1;i<=n;i++)res=max(res,f[i]);
    	if(res>=k)return 1;
    	else return 0;
    }
    
    int main() {
    	n=read();d=read();k=read();
    	for(int i=1;i<=n;i++) {
    		x[i]=read();s[i]=read();
    		if(s[i]>0)mx+=s[i];
    	}
    	if(mx<k){
    		puts("-1");return 0;
    	}
    	int h=0,t=max(x[n],d),ans;//*3
    	while(h<=t){
    		int mid=(h+t)>>1;
    		if(check(mid)) {
    			ans=mid;//*4
    			t=mid-1;
    		} else {
    			h=mid+1;
    		}
    	}
    	printf("%d
    ",ans);
    	return 0;
    } 
    

    例6:poj1821

    蓝书好题P310

    [先把工匠按照S[i]排序\ 设f[i][j]表示前i个工匠刷j块木板的最大报酬\ 第i个木匠可以不刷f[i][j]=f[i-1][j]\ 第j块木板可以空着不刷f[i][j]=f[i][j-1]\ 第i个木匠可以刷第k+1到第j块木板,k+1<=s[i]<=j 且 j-k<=l[i]\ f[i][j]=max(f[i-1][k]+p[i]*(j-k));其中(j>=s[i],j-l[i]<=k<=s[i]-1)\ 化简f[i][j]=p[i]*j+max(f[i-1][k]-=p[i]*k));其中(j>=s[i],j-l[i]<=k<=s[i]-1)\ ]

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    
    const int N=16010;
    struct node{
        int s,l,p;
        bool operator < (const node &x) const {
            return s<x.s;
        }
    }a[110];
    int f[110][N],n,m,q[N];
    
    int calc(int i,int k) {
        return f[i-1][k]-a[i].p*k;
    }
    int main() {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++) 
            scanf("%d%d%d",&a[i].l,&a[i].p,&a[i].s);
        sort(a+1,a+1+m);
        for(int i=1;i<=m;i++) {
            int l=1,r=0;
            for(int k=max(0,a[i].s-a[i].l);k<=a[i].s-1;k++) {
                while(l<=r&&calc(i,q[r])<=calc(i,k)) r--;
                q[++r]=k;
            }
            for(int j=1;j<=n;j++) {
                f[i][j]=max(f[i-1][j],f[i][j-1]);
                if(j>=a[i].s) {
                    while(l<=r&&q[l]<j-a[i].l) l++;
                    if(l<=r) f[i][j]=max(f[i][j],calc(i,q[l])+a[i].p*j);
                }
            }
        }
        printf("%d
    ",f[m][n]);
        return 0;
    }
    
    
  • 相关阅读:
    MacOS 上搭建 flutter 环境
    前后端分离项目获取后端跨控制器获取不到session
    记angular和asp.net使用grpc进行通信
    js实现encodeHTML和decodeHTML
    Angular写一个Form组件-TagInput
    NVidia-Docker2安装与常用命令
    ROIAlign
    php启动报错
    git clone 报错处理
    免二次请求,selenium直接保存图片元素到本地
  • 原文地址:https://www.cnblogs.com/ke-xin/p/13512717.html
Copyright © 2011-2022 走看看