单调队列

单调队列

单调队列是一种特殊维护的队列。一般地,当一新值准备入队时,须先从后向前,将对后来答案没有了影响的点弹出,再从前向后,将所有超出了统计范围的点弹出。对于大多数问题,求解过程中会锁定很多答案区间,在线求最值。

P3088 拥挤的奶牛

题面

有 $N$ 头奶牛沿着一维的栅栏吃草,第 $i$ 头奶牛在目标点 $x_i$,它的身高是 $h_i$ 。当一头奶牛左边 $D$ 距离内而且右边 $D$ 距离内有身高至少是它的两倍的奶牛,它就会觉得拥挤。

请计算觉得拥挤的奶牛的数量。

当一奶牛将要入队时:

  • 从后往前,将身高小于该奶牛的弹出(--tail),因为将要进队的奶牛更加靠后,影响范围一定更大,对答案的贡献也一定更大。
  • 从前往后,将与该奶牛距离已超过 $D$ 的奶牛弹出(++head
  • 将该奶牛入队。
  • 与队头奶牛比较身高,若符合则打上标记,若左右两次统计均符合,则计入答案。

每次统计的是该奶牛左侧的合法性,从后到前再做一遍就可以了。

按照以上规则就可以统计答案了。显然该队列一定为单调递降队列。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <stdio.h>
#include <algorithm>
using namespace std;

const int N = 55000;

struct node {
int x, h;
} a[N];

int n, d, queue[N];
bool l[N], r[N];

bool cmp(node a, node b) {
return a.x < b.x;
}
int main() {

freopen("p3088.in", "r", stdin);

scanf("%d%d", &n, &d);
for(int i=1; i<=n; ++i)
scanf("%d%d", &a[i].x, &a[i].h);

sort(a+1, a+n+1, cmp);

int head = 0, tail = 1;
queue[head] = 1;

for(int i=2; i<=n; ++i) {
while(head < tail and a[queue[tail-1]].h <= a[i].h) --tail;
queue[tail++] = i;
while(head < tail and a[i].x - a[queue[head]].x > d) ++head;

if(a[queue[head]].h >= (a[i].h << 1)) l[i] = true;
}

head = 0, tail = 1;
queue[head] = n;

for(int i=n-1; i>=1; --i) {
while(head < tail and a[queue[tail-1]].h <= a[i].h) --tail;
queue[tail++] = i;
while(head < tail and a[queue[head]].x - a[i].x > d) ++head;

if(a[queue[head]].h >= (a[i].h << 1)) r[i] = true;
}

int ans = 0;
for(int i=1; i<=n; ++i)
if(l[i] and r[i]) ++ans;

printf("%d\n", ans);

return 0;
}

P3522 Temperature

题面

有 $n$ 个段,第 $i$ 个为 $[l_i, r_i]$,现要在几个连续的段中,每段中各取一个值,构成一序列,要求该序列不能下降,求序列的最大长度。

以段的左端点维护单调序列。

当一新段将要入队时:

  • 从后向前,将所有左点比新段的左点小的段弹出。
  • 从前向后,将所有右点比新段的左点小的段弹出。
  • 统计答案:用当前位置减去队头的上一段的位置
    • $\because$ 队头的上一段已被弹出
    • $\therefore$ 从队头的上一段的下一段起(可能已被弹出)至当前位置均属于合法序列

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>

const int N = 1100000;

int max(int a, int b) { return a > b ? a : b; }

int n, l[N], r[N], queue[N], ans = -1;
int main() {

freopen("p3522.in", "r", stdin);

scanf("%d", &n);
for(int i=1; i<=n; ++i) scanf("%d%d", l+i, r+i);

int head = 0, tail = 1;
queue[head] = 0;

for(int i=1; i<=n; ++i) {

while(head < tail and l[i] >= l[queue[tail-1]]) --tail;
queue[tail++] = i;

while(head < tail and l[queue[head]] > r[i]) ++head;

ans = max(ans, i - queue[head-1]);

}

printf("%d\n", ans);

return 0;
}
发布于

2020-02-01

更新于

2023-01-11

许可协议

评论