Post

单调队列

单调队列

单调队列常用于解决定长区间最值问题(滑动窗口),一般使用一个 deque 来实现。

与单调栈类似,每次向队尾 push 新元素时,新元素显然“离得近”,如果新元素不仅“离得近”,优先级还比当前队尾元素更高(更大/小),那么在以后当前队尾元素必然不可能成为答案,可以将其 pop_back 弹出。由于求解的是区间最值,因此单调队列的队首即为答案,注意每次取出答案前要先检查队首元素是否“过期”,即在当前区间内。

P2032

裸的板子题。

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
#include <bits/stdc++.h>
using namespace std;
int n, k, a[maxN];
void solve() {
    cin >> n >> k;
    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    deque<int> q;
    for (int i = 0; i < n; i++) {
        while (!q.empty() && a[q.back()] < a[i]) {
            q.pop_back();
        }
        while (!q.empty() && q.front() <= i - k) {
            q.pop_front();
        }
        q.push_back(i);
        if (i >= k - 1) {
            cout << a[q.front()] << endl;
        }
    }
}
int main() {
    solve();
    return 0;
}

P2216

实际上是一个二维的滑动窗口问题。先对每行求一个区间最值,再对列求一个“行区间最值的最值”,就可以求出一个二维区域的最值。两步操作都可以用一个单调队列来实现。

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
#include <bits/stdc++.h>
using namespace std;
#define mi(x, y) ((x) < (y) ? x : y)
const int inf = 0x7fffffff, maxN = 2e5 + 5;
int a, b, n, m[1005][1005], maxx[1005][1005], minn[1005][1005], res = inf; // 表示第i行第j列往左n个数的最大最小值
int main() {
    cin.tie(0)->sync_with_stdio(0);
    cin >> a >> b >> n;
    for (int i = 1; i <= a; i++) {
        for (int j = 1; j <= b; j++) {
            cin >> m[i][j];
        }
    }
    for (int i = 1; i <= a; i++) {
        deque<int> qmax, qmin;
        for (int j = 1; j <= b; j++) {
            while (!qmax.empty() && m[i][j] >= m[i][qmax.back()]) {
                qmax.pop_back();
            }
            while (!qmax.empty() && qmax.front() <= j - n) {
                qmax.pop_front();
            }
            qmax.push_back(j);
            maxx[i][j] = m[i][qmax.front()];
            while (!qmin.empty() && m[i][j] <= m[i][qmin.back()]) {
                qmin.pop_back();
            }
            while (!qmin.empty() && qmin.front() <= j - n) {
                qmin.pop_front();
            }
            qmin.push_back(j);
            minn[i][j] = m[i][qmin.front()];
        }
    }
    for (int j = n; j <= b; j++) {
        deque<int> qmax, qmin;
        for (int i = 1; i <= a; i++) {
            while (!qmax.empty() && maxx[i][j] >= maxx[qmax.back()][j]) {
                qmax.pop_back();
            }
            while (!qmax.empty() && qmax.front() <= i - n) {
                qmax.pop_front();
            }
            qmax.push_back(i);
            while (!qmin.empty() && minn[i][j] <= minn[qmin.back()][j]) {
                qmin.pop_back();
            }
            while (!qmin.empty() && qmin.front() <= i - n) {
                qmin.pop_front();
            }
            qmin.push_back(i);
            if (i >= n)
                res = mi(res, maxx[qmax.front()][j] - minn[qmin.front()][j]);
        }
    }
    cout << res;
    return 0;
}

CF1918D

二分答案,check 函数内进行单调队列优化 dp。

dp[i] 表示阻塞 a[i],阻塞元素总和的最小值。每次转移,找前面所有满足“到 i 的这一段元素总和不超过 x”的位置里,dp 值最小的那一个进行转移。

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
//O2优化
#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
#define mi(x, y) ((x) < (y) ? (x) : (y))
#define ma(x, y) ((x) > (y) ? (x) : (y))    
const int maxN = 2e5 + 5;
const long long inf = 1e18;
int n, a[maxN];
long long dp[maxN], sum[maxN];
bool check(long long x) {
    deque<int> q;
    q.push_back(0);
    for (int i = 1; i <= n + 1; i++) {
        dp[i] = dp[q.front()] + a[i];
        while (!q.empty() && dp[q.back()] >= dp[i]) {
            q.pop_back();
        }
        while (!q.empty() && sum[i] - sum[q.front()] > x) {
            q.pop_front();
        }
        q.push_back(i);
    }
    return dp[n + 1] <= x;
}
void solve() {
    long long l = -1, r = 0, res = 0;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
        sum[i] = sum[i - 1] + a[i];
        l = ma(l, a[i]), r += a[i];
    }
    a[n + 1] = 0;
    while (l <= r) {
        long long mid = (l + r) >> 1;
        if (check(mid)) {
            res = mid, r = mid - 1;
        } else {
            l = mid + 1;
        }
    }
    cout << res << '\n';
}
int main() {
    cin.tie(0)->sync_with_stdio(0);
    int t;
    cin >> t;
    while (t--)
        solve();
    return 0;
}
This post is licensed under CC BY 4.0 by the author.