单调队列

本文深入探讨单调队列在解决特定类型问题中的应用,如最大值查询、最小总和选择、子矩阵最大面积等,通过实例讲解单调队列如何高效处理窗口内元素的最值问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最大值维护一个下降序列时,才能保证队头是最大值。

同理,最小值维护一个上升序列时,才能保证队头是最小值。

过渡题
给定一个n个数的数列,从左至右输出每个长度为m的数列段内的最大数。
比如8个数的数列[1 3 -1 -3 5 3 6 7],m=3,那么每连续3个最大值如下:
位置
最大值
[1 3 -1] -3 5 3 6 7
3
1 [3 -1 -3] 5 3 6 7
3
1 3 [-1 -3 5] 3 6 7
5
1 3 -1 [-3 5 3] 6 7
5
1 3 -1 -3 [5 3 6] 7
6
1 3 -1 -3 5 [3 6 7]
7
【输入格式】
第一行两个整数n和m( 1<= n <= 20 0000,m<=n)。
下来给出n个整数。
【输出格式】
一行一个整数,表示每连续m个数的最大值。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define gc getchar()
#define ll long long
using namespace std;
const int N=2e5+10;
inline void qr(ll &x)
{
    x=0;int f=1;char c=gc;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc;}
    while(c>='0'&&c<='9'){x=x*10+(c^48);c=gc;}
    x*=f;
}
inline void qw(ll x)
{
    if(x<0)x=-x,putchar('-');
    if(x/10)qw(x/10);
    putchar(x%10+48);
}
struct node
{
    int p;ll w;
    node(){}
    node(int p,ll w):p(p),w(w){}
}list[N];
int head,tail;
ll a[N],f[N];
int main()
{
    int n,m;scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)qr(a[i]);list[1]=node(1,a[1]);head=1,tail=1;
    for(int i=2;i<=n;i++)
    {
        while(head<=tail&&i-list[head].p>=m)++head;//踢队头(太远了) 
        while(head<=tail&&list[tail].w<a[i])--tail;//踢队尾(太小了) 
        list[++tail]=node(i,a[i]);f[i]=list[head].w;
    }
    for(int i=m;i<=n;i++)qw(f[i]),puts("");
    return 0;
}

模板题
【题意】
从N个数(N<=100000)中选若干个数,要求在连续M(M<=N)个数里至少要有一个数被选择。 求选出来数的最小总和。
【输入格式】
第一行两个整数 N,M;
接下来N个数ai(ai<=100)表示第i个数。
【输出格式】
一个整数,最小总和。
【样例输入】
5 3
1
2
5
6
2
【样例输出】
4

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define gc getchar()
#define ll long long
using namespace std;
const int N=2e5+10;
inline void qr(ll &x)
{
	x=0;int f=1;char c=gc;
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc;}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=gc;}
	x*=f;
}
inline void qw(ll x)
{
	if(x<0)x=-x,putchar('-');
	if(x/10)qw(x/10);
	putchar(x%10+48);
}
struct node
{
	int p;ll w;
	node(){}
	node(int p,ll w):p(p),w(w){}
}list[N];
int head,tail;
ll a[N],f[N];//f数组记录和
int main()
{
	int n,m;scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)qr(a[i]);list[1]=node(0,0);head=1,tail=1;
	for(int i=1;i<=n;i++)
	{
		//head保持最小,且有效。
		while(head<=tail&&i-list[head].p>m)++head;//连续m个数至少要选一个,那么两个数之间不能超过m个
		f[i]=list[head].w+a[i];//最小的head+a[i]
		while(head<=tail&&list[tail].w>f[i])--tail;//如果队尾的数大于当前,那么这个状态就没有意义
		list[++tail]=node(i,f[i]);
	}
	ll ans=(1<<30);
	for(int i=n-m+1;i<=n;i++)ans=min(ans,f[i]);qw(ans);puts("");
	return 0;
}

【题意】
一个数列有N(1 <= N <= 100,000)个数(0<=ai<=10^9),要求从中选若干个,使得选出来的数之和最大。不能选中超过连续M(M<=N)个。
【输入格式】
第一行两个整数N和M;
下来给出n个数。
【输出格式】
一行一个整数,表示最大的和。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define gc getchar()
#define ll long long
using namespace std;
const int N=2e5+10;
inline void qr(ll &x)
{
  x=0;int f=1;char c=gc;
  while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc;}
  while(c>='0'&&c<='9'){x=x*10+(c^48);c=gc;}
  x*=f;
}
inline void qw(ll x)
{
  if(x<0)x=-x,putchar('-');
  if(x/10)qw(x/10);
  putchar(x%10+48);
}
int head,tail;
int q[N];
ll a[N],f[N][2],s[N];
int main()
{
  int n,m;scanf("%d%d",&n,&m);
  for(int i=1;i<=n;i++)qr(a[i]),s[i]=s[i-1]+a[i];
  q[1]=0;head=1,tail=1;
  for(int i=1;i<=n;i++)
  {
  	f[i][0]=max(f[i-1][1],f[i-1][0]);
  	while(head<=tail&&i-q[head]>m)++head;
  	f[i][1]=f[q[head]][0]+(s[i]-s[q[head]]);
  	while(head<=tail&&s[i]-f[i][0]<s[q[tail]]-f[q[tail]][0])--tail;//意味着我选i比选tail时损失更小了
  	q[++tail]=i;
  }
  qw(max(f[n][1],f[n][0])),puts("");
  return 0;
}

【题意】
一个猴子要吃香蕉,一共N棵香蕉树排列在一条直线上,它一开始在第一棵树上。
每棵树上有不同数量的香蕉,猴子每次最多的跳跃距离为D,而且最多只能跳M次,问猴子最多能吃到多少香蕉?
【输入格式】
第一行 三个整数 N,D,M (M<N<=5000,D<=10000);
下面N行 每行两个整数 ai,bi (ai,bi<=1000000) 分别表示每棵树上的香蕉数目,以及每棵树的位置(树的位置是递增的)。
数据保证没有两棵香蕉树在同一位置,以及b[1]=0。
【输出格式】
一个整数,表示猴子最多吃到的香蕉数。
【样例输入】
5 5 2
6 0
8 3
4 5
6 7
9 10
【样例输出】
20

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#define gc getchar()
using namespace std;
const int N=5010;
struct node{int p,w;}a[N];
int last[N],now[N],q[N];
inline void qr(int &x)
{
   x=0;int f=1;char c=gc;
   while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc;}
   while(c>='0'&&c<='9'){x=x*10+(c^48);c=gc;}
   x*=f;
}
void qw(int x)
{
   if(x<0)x=-x,putchar('-');
   if(x/10)qw(x/10);
   putchar(x%10+48);
}
int main()
{
   int n,d,m;qr(n),qr(d),qr(m);
   for(int i=1;i<=n;i++)
   {
       qr(a[i].w),qr(a[i].p);
       if(a[i].p-a[i-1].p>d){m=min(i-1,m);break;}
   }
   int ans=now[1]=a[1].w,head,tail;
   for(int j=1;j<=m;j++)
   {
       memcpy(last,now,n<<2);
       q[1]=j;head=tail=1;
       for(int i=j+1;i<=n;i++)
       {
           while(head<=tail&&a[i].p-a[q[head]].p>d)++head;
           if(head>tail)break;now[i]=last[q[head]]+a[i].w;
           if(last[i])
           {
               while(head<=tail&&last[q[tail]]<last[i])--tail;
               q[++tail]=i;    
           }
           ans=max(ans,now[i]);
       }
   }
   qw(ans),puts("");
   return 0;
}

【题意】
给一个N∗*M的数矩阵。求一个子矩阵,要求子矩阵中最大值与最小值的差<=C,而且子矩阵的宽(横)不超过100,长(竖)没有限制。
求子矩阵的最大面积。
【输入格式】
第一行两个整数 M(左右方向),N(上下方向)和 C (N,M<=500 0<=C<= 10 );
接下来 N行 每行M个整数(每个数的范围为-30000至30000)。
【输出格式】
子矩阵的最大面积(长*宽)。
【样例输入】
10 15 4
41 40 41 38 39 39 40 42 40 40
39 40 43 40 36 37 35 39 42 42
44 41 39 40 38 40 41 38 35 37
38 38 33 39 36 37 32 36 38 40
39 40 39 39 39 40 40 41 43 41
39 40 41 38 39 38 39 39 39 42
36 39 39 39 39 40 39 41 40 41
31 37 36 41 41 40 39 41 40 40
40 40 40 42 41 40 39 39 39 39
42 40 44 40 38 40 39 39 37 41
41 41 40 39 39 40 41 40 39 40
47 45 49 43 43 41 41 40 39 42
42 41 41 39 40 39 42 40 42 42
41 44 49 43 46 41 42 41 42 42
45 40 42 42 46 42 44 40 42 41
【样例输出】
35

#include<cstdio>
#include<algorithm>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#define gc getchar()
using namespace std;
const int N=710;
inline void qr(int &x)
{
	x=0;int f=1;char c=gc;
	while(c<'0'||c>'9'){if(c=='-')f=-1;c=gc;}
	while(c>='0'&&c<='9'){x=x*10+(c^48);c=gc;}
	x*=f;
}
void qw(int x)
{
	if(x<0)x=-x,putchar('-');
	if(x/10)qw(x/10);
	putchar(x%10+48);
}
int a[N][N],wmax[N],wmin[N],l1,l2,r1,r2,q1[N],q2[N];
inline void ins1(int x)
{
	while(l1<=r1&&wmax[q1[r1]]<wmax[x])--r1;//维护一个下降序列 
	q1[++r1]=x;
}
inline void ins2(int x)
{
	while(l2<=r2&&wmin[q2[r2]]>wmin[x])--r2;//维护一个上升序列 
	q2[++r2]=x;
}
int main()
{
	int n,m,c;qr(m),qr(n),qr(c);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			qr(a[i][j]);
	int ans=0;
	for(int j=1;j<=m;j++)//左边界 
	{
		for(int i=1;i<=n;i++)wmax[i]=wmin[i]=a[i][j];
		int u=min(j+99,m);
		for(int i=j+1;i<=u;i++)//右边界 
		{
			for(int k=1;k<=n;k++)
				wmax[k]=max(wmax[k],a[k][i]),
				wmin[k]=min(wmin[k],a[k][i]);
			l1=l2=1;r1=r2=0;
			int head=1,tail=1,w=i-j+1;
			while(tail<=n&&(n-head+1)*w>ans)
			{
				ins1(tail),ins2(tail);
				while(head<=tail&&l1<=r1&&l2<=r2&&wmax[q1[l1]]-wmin[q2[l2]]>c)
				{
					++head;
					while(l1<=r1&&q1[l1]<head)++l1;
					while(l2<=r2&&q2[l2]<head)++l2;
				}
				ans=max(ans,(tail-head+1)*w);
				++tail;
			}
		}
	}
	qw(ans),puts("");
	return 0;
}

简单说一下最后一道题吧。

根据这道题,由于它的宽(横)有限制,而长(竖)无限制,那么我们便可以仅枚举左边界和右边界,就可以制造出一个又一个的子矩阵,通过单调队列来维护当前子矩阵的最大值与最小值,即可求解。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值