单调队列

定义和性质

一个队列内部的元素具有单调性的一种数据结构,分为单调递增队列和单调递减队列。
单调递减队列队首元素为区间最大值,队尾元素为最近的大于新元素的元素下标。
单调递减序列维护区间最小值和最近的小于新元素的元素下标。


模板

int l=1,r=0;
for(int i=1;i<=n;i++){
	while(r>=l&&a[q[r]]>a[i]) r--;
	q[++r]=i;
}

常见用法

给定一个n个数的数列,从左至右输出每个长度为m的区间内的最小数。

int l=1,r=0;
for(int i=1;i<=n;i++){
	while(r>=l&&a[q[r]]>=a[i]) r--;
	q[++r]=i;
	while(r>=l && q[l]<i-m+1)l++;
}

给定一个长度为n的数列,每一个点分别有 x,y 两个值,找到最长的区间,满足区间中 max(x[i]) - min(y[i]) <= k

int l1=1,r1=0,l2=1,r2=0,now=1;
for(int i=1;i<=n;i++){
	while(r1>=l1&&a[q1[r1]]<a[i]) r1--;//单调不严格减 
	while(r2>=l2&&a[q2[r2]]>a[i]) r2--;//单调不严格增 
	q1[++r1]=i;
	q2[++r2]=i;
	while(r1>=l1&&r2>=l2&&a[q1[l1]]-a[q2[l2]]>k){
		if(q1[l1]<q2[l2]) now=q1[l1++]+1;//先移动坐标小的 
		else  now=q2[l2++]+1;
	}
	ans=max(ans,i-now+1);
} 

例题

Feel Good https://cn.vjudge.net/contest/314716#problem/C

给定区间 ( 1 , n ) (1,n) (1,n),定义 f ( a , b ) f(a,b) f(a,b)为区间和*区间最小值,求 m i n ( f ( a , b ) ) , 1 ≤ a ≤ n , 1 ≤ b ≤ n min(f(a,b)),1\le a \le n,1 \le b\le n min(f(a,b))1an1bn

解法:
将问题转化为,求每个元素作为最小值时的最大区间,计算出每个区间的贡献,对答案求max

//用stl_stack的写法,常数大不推荐
#include <iostream>
#include <cstdio>
#include <stack>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
ll a[maxn],sum[maxn],p1[maxn],p2[maxn];
int main(){
	int n;
	cin>>n;
	for(int i=1;i<=n;i++){
		scanf("%lld",&a[i]);
		sum[i]=a[i]+sum[i-1];
	}
	stack<ll>s;
	for(int i=1;i<=n;i++){
		while(!s.empty()&&a[s.top()]>=a[i]) s.pop();
		if(s.empty()) p1[i]=1;
		else p1[i]=s.top()+1;
		s.push(i); 
	}
	while(!s.empty()) s.pop();
	for(int i=n;i>=1;i--){
		while(!s.empty()&&a[s.top()]>=a[i]) s.pop();
		if(s.empty()) p2[i]=n;
		else p2[i]=s.top()-1;
		s.push(i); 
	}
	ll ans=-1,tx=0,ty=0;
	for(int i=1;i<=n;i++){
		ll temp=(sum[p2[i]]-sum[p1[i]-1])*a[i];
		if(temp>ans){
			ans=temp;
			tx=p1[i];ty=p2[i];
		}
	}
	printf("%lld\n%lld %lld",ans,tx,ty);
}

Ascending Rating https://cn.vjudge.net/contest/314716#problem/D

给定区间(1,n),定义f(i,j)为区间(i,j)上的最大值,g(i,j)为从左往右遍历该区间上最大值的更新次数,求 Σ f ( i , i + m ) 和 Σ g ( i , i + m ) , ( 1 ≤ i , i + m ≤ n ) \Sigma f(i,i+m)和\Sigma g(i,i+m),(1\le i,i+m \le n) Σf(i,i+m)Σg(i,i+m)(1ii+mn)

解法:
最大值同上可以用坐标递增值递减的单调队列的队首元素表示,而更新次数可以用坐标递减值递减的单调队列的大小来表示,发现最大值也可以用后者的来维护,因此只需要从右往左建立递减的单调队列即可

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn=1e7+5;
ll a[maxn],q1[maxn],q2[maxn];
ll ans1[maxn],ans2[maxn];
int main(){
	ll T,n,m,k,p,q,r,mod;
	scanf("%lld",&T);
	while(T--){
		scanf("%lld%lld%lld%lld%lld%lld%lld",&n,&m,&k,&p,&q,&r,&mod);
		for(int i=1;i<=k;i++)
			scanf("%lld",&a[i]);
		for(int i=k+1;i<=n;i++)
			a[i]=(p*a[i-1]+q*i+r)%mod;
		ll res1=0,res2=0;
		ll l=n+1,r=n;
		for(int i=n;i>=1;i--){
			while(r>=l&&a[q2[l]]<=a[i]) l++;
			q2[--l]=i;
			while(r>=l&&q2[r]>i+m-1) r--;
			if(i<=n-m+1){
				res2+=(r-l+1)^i;
				res1+=a[q2[r]]^i;
			}
		}
		printf("%lld %lld\n",res1,res2);
	}
}

OpenStreetMap https://cn.vjudge.net/contest/314716#problem/G

给定矩阵M[n][m],a,b,对于M中每个a*b的子矩阵,求出子矩阵中的最小元素并求和

解法:
遍历每一行,从左至右存储每个长度为b的区间内的最小数,得到新矩阵B[n][m-b+1],遍历B的每一列,同样用单调递增队列就可以维护得到每个子矩阵的最小值。

#include <iostream>
using namespace std;
typedef long long ll;
const int maxn=3e3+5;
int h[maxn][maxn],g[maxn*maxn],q[maxn];
int a1[maxn][maxn];
int main(){
	int n,m,a,b,x,y,z;
	scanf("%d%d%d%d",&n,&m,&a,&b);
	scanf("%d%d%d%d",&g[0],&x,&y,&z);
	for(int i=1;i<=n*m-1;i++)
		g[i]=((ll)g[i-1]*(ll)x%z+(ll)y)%z;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			h[i][j]=g[(i-1)*m+j-1];
	for(int i=1;i<=n;i++){//算出每个横区间的最小值矩阵 
		int l=1,r=0;
		for(int j=1;j<=m;j++){
			while(r>=l&&h[i][q[r]]>h[i][j]) r--;//不严格增(最小值)
			q[++r]=j;
			while(r>=l&&j-q[l]+1>b)l++;
			if(j>=b) a1[i][j]=h[i][q[l]];
		}
	}
	ll ans=0;
	for(int j=b;j<=m;j++){
		int l=1,r=0;
		for(int i=1;i<=n;i++){
			while(r>=l&&a1[q[r]][j]>a1[i][j]) r--;
			q[++r]=i;
			while(r>=l&&i-q[l]+1>a)l++;
			if(i>=a)
				ans+=a1[q[l]][j];
		}
	}
	printf("%lld\n",ans);
}

Planting Trees https://ac.nowcoder.com/acm/contest/883/F

给定一个n*n的矩阵和每个位置上的权值 ai,j ,求最大的子矩阵,满足子矩阵中最大值和最小值之差不超过m。

解法:
枚举子矩阵的上边与下边O(N^2),存储区间内n条宽度为1的矩阵的最小值和最大值,在下界递增的时候最小值与最大值的维护是O(1)的,每条矩阵抽象成了一个点(有两个值),此时可以用单调队列在O(N)内求出最大值最小值相差不超过m的最大区间。

#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
const int maxn=505;
int a[maxn][maxn];
int maxx[maxn],minn[maxn];
int q1[maxn],q2[maxn];
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		int n,m;
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				scanf("%d",&a[i][j]);
		int ans=0;
		for(int t=1;t<=n;t++){//枚举上界
			for(int i=1;i<=n;i++)//初始化最大最小值 
				minn[i]=maxx[i]=a[t][i];
			for(int b=t;b<=n;b++){//枚举下界
				if(b!=t){
					for(int i=1;i<=n;i++){
						minn[i]=min(minn[i],a[b][i]);
						maxx[i]=max(maxx[i],a[b][i]);
					}
				}			
				int l1=1,r1=0,l2=1,r2=0,now=1;
				for(int i=1;i<=n;i++){
					while(r1>=l1 && maxx[q1[r1]]<maxx[i]) r1--;//不严格递减(最大值) 
					while(r2>=l2 && minn[q2[r2]]>minn[i]) r2--;//不严格递增(最小值)
					q1[++r1]=i;
					q2[++r2]=i;
					while(r1>=l1&&r2>=l2&&maxx[q1[l1]]-minn[q2[l2]]>m){
						if(q1[l1]<q2[l2])now=q1[l1++]+1;
						else now=q2[l2++]+1;
					}
					ans=max(ans,(i-now+1)*(b-t+1));	
				}
			}
		}
		printf("%d\n",ans);
	}
}
Python的单调队列是一种用于解决某些特定问题的数据结构。它是队列的一种变体,可以快速查询当前队列中的最大或最小元素。 单调队列通常用于需要维护当前滑动窗口中的最大或最小的情况。例如,假设我们有一个长度为n的数组arr和一个窗口大小为k的滑动窗口。我们想要找到每个窗口中的最大单调队列就可以帮助我们在O(n)的时间复杂度内实现。 实现单调队列需要两个操作:push(x)和pop()。push(x)用于向队列的尾部添加元素x,而pop()用于从队列的头部删除元素。这两个操作具有O(1)的时间复杂度。 当我们向队列中添加一个新元素时,为了维护队列的单调性,我们需要从队列的尾部删除一些元素。具体来说,我们从队列的尾部开始,不断地删除比新元素小的元素,直到队列为空或者新元素大于等于队列尾部元素为止。这样,我们就可以保证队列中的元素是以递减顺序排列的。 当我们需要查询当前队列中的最大或最小时,只需访问队列头部的元素即可。由于队列中的元素是以递减的顺序排列的,所以头部元素就是最大(或最小)。 总的来说,Python的单调队列是一种高效的数据结构,可以用于解决一些特定问题,如滑动窗口中的最大(或最小)。它具有O(n)的时间复杂度,并且可以通过push(x)和pop()操作来维护队列的单调性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值