一,单调栈
思想:
可以用于维护一个数前/后第一个大于/小于他的数。我们以维护最大值为例
- 对于数x,求取它右边第一个大于他的数y。显然,[x,y]两个数中间的数都是比x小的。也就是,我们在求出x右边第一个大于它的数之前,一定能够先找到中间这些数的右边第一个比他大。(不可能超过y,因为中间数mid<=x<=y).
- 利用这个思想,我们把还未求出右边第一大的数放入栈里面,如果找到栈顶的右边第一大,弹出栈顶。否则,栈顶更新为这个更小的数(它一定不会比原栈顶慢找到右边第一大)
- 那么如何求取左边第一大呢?显然,我们在比较栈顶时就已经实现比较了,求目前数x的左边第一大。
- 如果栈顶k比x大,显然k就是x的左边第一大(栈里面的元素显然都比k大,[k,x]之间不存在比k大的数,否则k早就被弹出了。
- 如果k小于x,等于k找到右边第一大,弹出k,继续比较x与新栈顶,直到栈空或者找到比k大。
- 但是你会发现,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)
- 我们用单调队列维护最大值,即队列是递减的。队列存储的元素至多是包含当前位置的前k个数
- 设当前遍历到i,值为x。如果x比队尾大,就删除队尾,直到队列空或者队尾比x大。(因为x比队列里的元素晚出现,所以队列里比他小的数,后面都没有他们什么事了)
- 如果x比队尾小,放入队尾。
- 注意,每次插入,先判断队首是否下标符合[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
- 我们求区间[L,R]是否是合法区间,否则,显然是需要把区间继续扩大。
- 所以,我们可以先固定L,不断往右移动R,显然第一个符合条件的R是最优的,那么,我们这时才把L往后移动。
- 这样遍历只需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;
}