单调栈
单调栈
本文代码适用于c++
例题均来自luogu
引入
先看一道例题:https://www.luogu.com.cn/problem/B3666
分析
- 显然,第i次操作后,ai一定是后缀最大值。
- 不难发现,若ai在第n次操作后(i<n)为后缀最大值的必要条件是ai在第i+1~第n-1次都为后缀最大值。
- 更进一步,我们得到一个类似递推的结论:
若ai在第n-1次操作中是最大后缀值(i<n),且ai>an是ai在第n次操作后是最大后缀值的充要条件。
我们定义一个数组ans:里面的元素是当前状态下a中所有的后缀最大值的下标。
样例数据
5
2 1 3 5 4
n=1时: 由分析1:将1加入到数组ans中;
ans: 1
n=2时: 由分析2:a2<a1,故a1仍在ans中;
由分析1:将2加入到数组ans中;
ans: 1 2
n=3时: 由分析2: a3>a2,故将a2删去;a3>a1,故将a1删去;
由分析1:将3加入ans;
ans: 3
n=4时:由分析2:a4>a3,故将a3删去;
由分析1:将4加入ans;
ans: 4
n=5时: 由分析2:a5<a4,故a4仍然留在ans;
由分析1: 将5加入ans;
ans: 4 5
由题目要求,只需要在每次操作后输出ans中所有元素的异或和即可。
不难发现,在维护数组ans的过程中我们只对最后一个元素进行操作,所以我们可以用栈来实现ans的维护。
代码实现
//注意:使用STL需开O2优化
#include<bits/stdc++.h>
#define int unsigned long long
#define mod 90007
#difine inf 0x7fffffff
using namespace std;
int n;
struct node{
int x,y;
}a[3000000];//创建数组,x为数字,y为下标
stack<node>s;
signed main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i].x;
a[i].y=i; //下标为输入时的顺序
}
int v=1;
int ans=0;
while(v<=n){ //v==n时最后一个元素还没有入栈,所以v=n+1时才能退出
while(!s.empty()&&s.top().x<a[v].x){
ans^=s.top().y; //出栈时异或
s.pop(); //维护栈的单调性,小于她的都出栈
}
s.push(a[v]); //入栈
ans^=a[v].y; //异或新入栈的
cout<<ans<<endl; //输出答案
v++; //准备下一个下标
}
}
举一反三
在上面我们利用维护一个内部元素单调的栈(严格说是栈内元素对应的在a中的数单调)后缀最大值问题。
下面我们再通过两道例题深入理解单调栈的“精神”;
分析
本题与上面不同的是:本题要求子区间的后缀最大值。我们容易想到利用双端队列的出入队操作来将问题转化。(或许可以叫单调队列???)
代码实现
(下面用数组实现双端序列 也可直接用STL)
#include<bits/stdc++.h>
#define N 1000009
#define int unsigned long long
using namespace std;
int n, k, x[N], q[N];//q为单调队列 存储最大后缀值下标
signed main()
{
cin >> n >> k;
for (int i = 1; i <= n; i++)cin >> x[i];
int l = 0, r = 0;
for (int i = 1; i <= n; i++)
{
while (l < r && i - q[l] >= k)l++;//当检查的子列的长度大于k且队列非空时 队首出队
while (l < r && x[i] >=x[q[r-1]])r--;//当队尾元素小于待入队元素且队列非空时 队尾出队
q[r++] = i;//i入队
if (i >= k)cout << r - l << endl;
}
return 0;
}
分析
对于区间[l,r],若任意aj>al(r>=j>l),ar=max{ai},al=min{ai};
则从l头奶牛到r头奶牛符合条件。
我们不难发现:al是区间[1,r]的后缀最小值,ar是从右往左看数组a([l,n])的倒数第二个后缀最大值。我们通过单调栈寻找l和r,最后输出r-l+1的最大值即可。
代码实现
#include<bits/stdc++.h>
const int maxn = 100005;
int n, ans, tx, tn;
int a[maxn], sx[maxn], sn[maxn];
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%d", a + i);
for (int i = 1; i <= n; ++i) {
while (tn && a[sn[tn]] >= a[i]) --tn;
while (tx && a[sx[tx]] < a[i]) --tx;
int k = std::upper_bound(sn + 1, sn + 1 + tn, sx[tx]) - sn;
if (k != (tn + 1)) {
ans = std::max(ans, i - sn[k] + 1);
}
sn[++tn] = i;
sx[++tx] = i;
}
printf("%d\n", ans);
return 0;
}

浙公网安备 33010602011771号