理解单调栈和单单调队列之前,要明白一种技巧,叫做尺取法。
尺取法
尺取法,两个位置,一个是l
,一个r
,r
一位位的左移,l
根据条件左移。比如POJ 3061
求最大连续字串和不超过s
l r
初始化为0
,r
左移,总和加上r
位置的值,如果总和一旦大于s
,l
开始左移,直到满足[l,r]
区间的总和小于s
,这种通过l,r
反复推进的方法,就叫尺取法。
细心的人可以发现,这种方法,求结果一定要满足,右边一个位置跨过
r
到l
一定是不合法的的情况。
单调栈
其实这个和尺取法关系不大。。。。
单调栈,用于求最左边(右边)的第一个满足某种具有单调性质条件*(比如大于,小于)*的位置。
距离,求第一个大于的位置。求大于,将不大于的数全部加入单调栈里面,保证栈单调递减,(下面距离栈中存的是小标,注意区分值和下标)
假设有1 3 5 2 1 4 7 6
一开始栈为空,将第一个数位置加进去,此时栈中有下标1
到第二个数 3
,栈顶 位置的值 小于3
弹出,赋值位置右边第一个大于他的数下标是2
,然后栈为空,将2
号位置加入栈,依次类推.
到5
弹出下标2
加入下标3
,到 2
因为栈顶位置的值大于2
不弹出,直到4
弹出值2,1
,不弹出5
。
当前值比栈顶的数大,弹出栈顶的值,并赋值,否则加入栈。
单调队列
单调队列和尺取法用点像,和单调栈也差距不大。总结来说,位置尺取,队列单调,就想尺取法和单调栈的结合体。例子 HDU 3530
按题目来讲: 查寻区间最大最小值之差在[m,k]之间的最大长度。
现按照尺取的方法来:不断移动,l,r
,虽然从在r
右边跨过r
到l
最大最小值之差绝对是大于等于[r,l]
之间的,但是最大最小不可能一直是端点,可能[r,l]
之间存在比r,l
大或者小的值。那么该如何处理呢,单调栈可以保存一个单调的子序列,我们是否能和他一样保存[l,r]
,的单增子序列呢?很显然是可以的。因为我们的[l,r]
有两端,所以用栈是肯定不行的,那么就只能用双端队列。我们建立两个双端队列,一个保存[l,r]
递增的子序列,一个保存[l,r]
递减的子序列,那么递减队列中第一个值保存的就是[l,r]
区间的最大值,递增的就是最小值。那么假设[l,r]
区间的最大值和最小值不满足条件了,要尺取就要移动l
,怎么移动?我们要改变最大最小值。改变哪里一个?肯定是离r
最远的那一个,我们单调队列保存的下标,直接把l
移动到两个队列队首,最小的下标+1
,然后弹出队首,就相当于找到了新[l,r]
最大(最小)值 (因为我们队列是单调的,弹出了最大(最小)值就相当于找到了次大(次小)值,在新区间就没有比他小的了 ) 然后,一直循环直到满足条件为止。
单增队列就是队尾进,每次和单调栈一样保证队列中是单调的,队首永远都是最小的或者最大的
HDU 3530 AC代码
#include "bits/stdc++.h"
using namespace std;
typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int, int> P;
#define VNAME(value) (#value)
#define bug printf("*********\n");
#define debug(x) cout<<"["<<VNAME(x)<<" = "<<x<<"]"<<endl;
#define mid (l + r) / 2
#define chl 2 * k + 1
#define chr 2 * k + 2
#define lson l, mid, chl
#define rson mid + 1, r, chr
#define pb push_back
#define mem(a, b) memset(a, b, sizeof(a));
const LL mod = (LL) 1e9 + 7;
const int maxn = (int) 1e6 + 5;
const int INF = 0x7fffffff;
const LL inf = 0x3f3f3f3f;
const double eps = 1e-8;
#ifndef ONLINE_JUDGE
clock_t prostart = clock();
#endif
void f() {
#ifndef ONLINE_JUDGE
freopen("../data.in", "r", stdin);
#endif
}
struct deq {
static const int start = maxn * 2;
int dat[start * 2];
int l = start;
int r = start;
void push_front(int x) {
dat[l--] = x;
}
void pop_front() {
l++;
}
int front() {
return dat[l + 1];
}
void push_back(int x) {
dat[++r] = x;
}
void pop_back() {
r--;
}
int back() {
return dat[r];
}
int size() {
return r - l;
}
void clear() {
r = l = start;
}
bool empty() {
return r == l;
}
};
deque<int> dq, dq2;
int n, m, k;
int a[maxn];
int main() {
f();
while (~scanf("%d%d%d", &n, &m, &k)) {
int ans = 0;
dq.clear();
dq2.clear();
int pos = 0;
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
// 大的弹出所以最前面是最小的
while (dq.size() > 0 && a[dq.back()] > a[i]) {
dq.pop_back();
}
dq.push_back(i);
while (dq2.size() > 0 && a[dq2.back()] < a[i]) {
dq2.pop_back();
}
dq2.push_back(i);
while (a[dq2.front()] - a[dq.front()] > k) {
if (dq2.front() < dq.front()) {
pos = dq2.front();
dq2.pop_front();
} else {
pos = dq.front();
dq.pop_front();
}
}
if (dq2.size() && dq.size() && a[dq2.front()] - a[dq.front()] >= m) {
ans = max(ans, i - pos);
}
}
printf("%d\n", ans);
}
#ifndef ONLINE_JUDGE
cout << "运行时间:" << 1.0 * (clock() - prostart) / CLOCKS_PER_SEC << "s\n";
#endif // ONLIN_JUDGE
return 0;
}
/*
5
4
*/
友情提示:最好别用 std 里面的栈和队列,太慢了。
进阶题:2019牛客暑期多校训练营(第三场)F Planting Trees
题解 : 2019牛客暑期多校训练营(第三场)F Planting Trees