单调栈与单调队列

文章介绍了单调栈和单调队列这两种数据结构在计算机科学中的应用,特别是它们如何用于维护序列中的最大值或最小值。单调栈用于找到一个数左边或右边的第一个大于或小于它的数,而单调队列则在保持元素进入顺序的同时维护区间单调性,适用于滑动窗口最大值等问题。文中提供了具体的代码示例来展示这两种数据结构的使用方法。
摘要由CSDN通过智能技术生成

一,单调栈

思想:

可以用于维护一个数前/后第一个大于/小于他的数。我们以维护最大值为例

  1. 对于数x,求取它右边第一个大于他的数y。显然,[x,y]两个数中间的数都是比x小的。也就是,我们在求出x右边第一个大于它的数之前,一定能够先找到中间这些数的右边第一个比他大。(不可能超过y,因为中间数mid<=x<=y).
  2. 利用这个思想,我们把还未求出右边第一大的数放入栈里面,如果找到栈顶的右边第一大,弹出栈顶。否则,栈顶更新为这个更小的数(它一定不会比原栈顶慢找到右边第一大)
  3. 那么如何求取左边第一大呢?显然,我们在比较栈顶时就已经实现比较了,求目前数x的左边第一大。
    1. 如果栈顶k比x大,显然k就是x的左边第一大(栈里面的元素显然都比k大,[k,x]之间不存在比k大的数,否则k早就被弹出了。
    2. 如果k小于x,等于k找到右边第一大,弹出k,继续比较x与新栈顶,直到栈空或者找到比k大。
    3. 但是你会发现,k等于x怎么办?我们可以在入栈时,记录每个元素栈里面比它严格大的值的下标,如果k=x,x存储k所记录的比他大的下标即可(可能没有)

P5788 【模板】单调栈 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

#include <bits/stdc++.h>
using namespace std;
#define ll               long long
#define endl             "\n"
#define int              long long
#define endll            endl<<endl
typedef unsigned long long ull;
typedef pair<int, int> pii;
typedef pair<long long, long long> pll;
//---------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------//
//double 型memset最大127,最小128
const int INF = 0x3f3f3f3f;         //int型的INF
const ll llINF = 0x3f3f3f3f3f3f3f3f;//ll型的llINF
const int N = 3e6 + 10;
int a[N];
void mysolve()
{
	stack<pii>s;
	int n,x;
	cin>>n;
	for(int i=1; i<=n; ++i)
		{
			cin>>x;
			while(!s.empty()&&s.top().first<x)
				{
					a[s.top().second]=i;
					s.pop();
				}
			s.push({x,i});
		}
	for(int i=1; i<=n; ++i)cout<<a[i]<<" \n"[i==n];
}

int32_t main()
{
	std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	ll t=1;
	while (t--)
		{
			mysolve();
		}
	system("pause");
	return 0;
}

 二,单调队列

单调队列的元素在按照进入顺序排序的同时维护了区间单调性。

P1886 滑动窗口 /【模板】单调队列 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

  1. 我们用单调队列维护最大值,即队列是递减的。队列存储的元素至多是包含当前位置的前k个数
  2. 设当前遍历到i,值为x。如果x比队尾大,就删除队尾,直到队列空或者队尾比x大。(因为x比队列里的元素晚出现,所以队列里比他小的数,后面都没有他们什么事了)
  3. 如果x比队尾小,放入队尾。
  4. 注意,每次插入,先判断队首是否下标符合[i-k+1,i]的区间,否则舍去。
#include <bits/stdc++.h>
using namespace std;
#define ll               long long
#define endl             "\n"
#define int              long long
typedef pair<int, int> pii;
//---------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------//
//double 型memset最大127,最小128
const int INF = 0x3f3f3f3f;         //int型的INF
const ll llINF = 0x3f3f3f3f3f3f3f3f;//ll型的llINF
const int N = 3e5 + 10;

void mysolve()
{
	int n,k;
	cin>>n>>k;
	deque<pii>q1,q2;
	vector<int>a1,a2;
	int x;
	for(int i=1; i<=n; ++i)
		{
			cin>>x;
			if(i<k)
				{
					while(!q1.empty()&&q1.back().first<x)q1.pop_back();
					q1.push_back({x,i});

					while(!q2.empty()&&q2.back().first>x)q2.pop_back();
					q2.push_back({x,i});
				}
			else
				{
					while(!q1.empty()&&q1.front().second<=i-k)q1.pop_front();
					while(!q1.empty()&&q1.back().first<x)q1.pop_back();
					q1.push_back({x,i});
					a1.push_back(q1.front().first);

					while(!q2.empty()&&q2.front().second<=i-k)q2.pop_front();
					while(!q2.empty()&&q2.back().first>x)q2.pop_back();
					q2.push_back({x,i});
					a2.push_back(q2.front().first);
				}
		}
	for(int i=0; i<=n-k; ++i)
		{
			if(!i)cout<<a2[i];
			else cout<<" "<<a2[i];
		}
	cout<<endl;
	for(int i=0; i<=n-k; ++i)
		{
			if(!i)cout<<a1[i];
			else cout<<" "<<a1[i];
		}
}

int32_t main()
{
	std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	ll t=1;
	//cin >> t;
	while (t--)
		{
			mysolve();
		}
	system("pause");
	return 0;
}

例题:P2698 [USACO12MAR]Flowerpot S 

  1. 我们求区间[L,R]是否是合法区间,否则,显然是需要把区间继续扩大。
  2. 所以,我们可以先固定L,不断往右移动R,显然第一个符合条件的R是最优的,那么,我们这时才把L往后移动。
  3. 这样遍历只需O(n)。
#include <bits/stdc++.h>
using namespace std;
#define ll               long long
#define endl             "\n"
#define int              long long
typedef pair<int, int> pii;
//---------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------//
//double 型memset最大127,最小128
const int INF = 0x3f3f3f3f;         //int型的INF
const ll llINF = 0x3f3f3f3f3f3f3f3f;//ll型的llINF
const int N = 3e5 + 10;
pii a[N];
void mysolve()
{
	int n,d;
	cin>>n>>d;
	int ans=INF;
	for(int i=1; i<=n; ++i)cin>>a[i].first>>a[i].second;
	deque<pii>q1,q2;
	sort(a+1,a+1+n);
	int p=-1;
	for(int i=1; i<=n; ++i)
		{
			while(!q1.empty()&&q1.back().first<a[i].second)q1.pop_back();
			q1.push_back({a[i].second,a[i].first});

			while(!q2.empty()&&q2.back().first>a[i].second)q2.pop_back();
			q2.push_back({a[i].second,a[i].first});

			if(q1.front().first-q2.front().first>=d)
				{
					ans=min(ans,abs(q1.front().second-q2.front().second));//队列的队首就是整个队列下标最小的,所以最大值与最小值的L就是min那一个
					p=min(q1.front().second,q2.front().second);
					while(!q1.empty()&&q1.front().second<=p)q1.pop_front();//合法后L右移,即删除小标小于等于L的元素
					while(!q2.empty()&&q2.front().second<=p)q2.pop_front();
				}
		}
	cout<<(ans<INF?ans:-1)<<endl;
}

int32_t main()
{
	std::ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	ll t=1;
	//cin >> t;
	while (t--)
		{
			mysolve();
		}
	system("pause");
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值