HDU3415 单调队列

这题是单调队列的典型运用。

至于单调队列,就是一个双端队列,在队首 (ff) 出队,在队尾 (bb) 出队入队,我们要维护整个队列的元素是单调的,比如,我们要动态查询从左向右的区间的最小值,那么我们就要在队列中维护一个单调递增的序列,从左向右枚举,队列的元素还有一个 idid 值,代表这个元素在原序列中的位置,然后左边的元素如果不在范围内了,就判断队首的元素 idid 是否是这个左边的 id,是的话就出队,否则就不管。关于元素入队,首先判断入队的元素是否大于队尾的元素 (保证队列单调递增),如果不大于,那么弹出队尾元素,直到队尾元素小于入队元素或者队列为空。

枚举 sum[i]sum[i] 表示前 ii 个元素的和,注意这里为了实现循环的序列要将 nn 扩展到 2n2n
然后枚举每一个 sum[i]sum[i],找到 ii 之前 kk 个元素 [ik,j][i-k, j] 区间内的 sumsum 最小值 sum[k]sum[k]sum[i]sum[k]sum[i] - sum[k] 就是最优解,然后取全局最优解即可。
注意一点在队列中在处理队列之前就要先将 ik1i - k - 1 号元素删除,我们要在队列维护最多 k+1k + 1 个元素,然后去最值,最后才将 sum[i]sum[i] 入队。
复杂度 O(n)O(n)

我的代码:

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
57
58
59
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>

using namespace std;

const int MAX = 200010;
const int oo = 0x3f3f3f3f;

int q[MAX], val[MAX], id[MAX], f, b;

int main() {
int t, n, k;
freopen("in.txt", "r", stdin);
scanf("%d", &t);
while (t--) {
scanf("%d%d", &n, &k);
for (int i = 0; i < n; i++) {
scanf("%d", &val[i]);
val[i + n] = val[i];
}
n *= 2;
for (int i = 1; i < n; i++) {
val[i] += val[i - 1];
}
f = b = 0;
int ret = -oo, st, ed;
for (int i = 0; i < n; i++) {
if (f != b && id[f] == i - k - 1) f++;
if (i < k && ret < val[i]) {
ret = val[i];
st = 1;
ed = i % (n / 2) + 1;
}
if (f == b) {
if (i && ret < val[i] - val[i - 1]) {
ret = val[i] - val[i - 1];
st = i % (n / 2) + 1;
ed = i % (n / 2) + 1;
} else if (ret < val[i]) {
ret = val[i];
st = ed = i % (n / 2) + 1;
}
} else {
if (ret < val[i] - val[id[f]]) {
ret = val[i] - val[id[f]];
st = (id[f] + 1) % n + 1;
ed = i % (n / 2) + 1;
}
}
while (f != b && q[b - 1] > val[i]) b--;
id[b] = i;
q[b++] = val[i];
}
printf("%d %d %d\n", ret, st, ed);
}
return 0;
}