单调队列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;
}

原文地址:https://www.cnblogs.com/ke-xin/p/13512717.html