[learning log] monotone stack monotone queue

Posted by mikejs on Sat, 08 Jan 2022 03:42:23 +0100

Monotone stack

Monotone stack is actually to maintain that the sequence in the stack is monotonous. When the element pressed into the monotone stack will break the monotonicity, the element inside will pop up until the monotonicity can be satisfied when the element is pressed into the stack.

vector<int>v;
stack<int>s;
for (int i = 1; i <= n; ++ i){
    while (!s.empty() && v[s.top()] >= v[i]){  // Is it equal to looking at the topic
        s.pop();
    }
    s.push(i);
}

Example: Luogu P5788

This question is for the third i i After i elements, from i + 1 i+1 i+1 element to n n The first of n elements is greater than a i a_i The subscript of the element of ai ^ reverses the sequence to find the last element larger than it in the elements before this element. Then, using the monotone stack to maintain monotonicity, maintain a decreasing sequence from back to front.

code

#include <bits/stdc++.h>
using namespace std;

const int LEN = 3e6 + 115;
int arr[LEN] = {0, }, ans[LEN] = {0, }, n;
stack<int>s;

int main(){
#ifndef ONLINE_JUDGE
    freopen("in.in", "r", stdin);
#endif
    std::ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    cin >> n;
    for (int i = 1; i <= n; ++ i){
        cin >> arr[i];
    }
    for (int i = n; i > 0; -- i){
        while (!s.empty() && arr[s.top()] <= arr[i]){
            s.pop();
        }
        if (s.empty()){
            ans[i] = 0;
        }else{
            ans[i] = s.top();
        }
        s.push(i);
    }
    for (int i = 1; i <= n; ++ i){
        cout << ans[i] << ' ';
    }
    return 0;
}

Monotone queue

Monotone queue is the same as monotone stack. There are monotone sequences in the queue (monotone refers to a user-defined state, such as increasing or non decreasing). Different from ordinary queues, both ends of a monotonous queue can pop up, but the queue entry operation is still carried out in a period.

Example: Luogu P1714

Topic analysis

The topics need to be found are the most in the series m m The maximum value of the sum of m numbers. If there are 0 numbers, the answer is 0. The best way to quickly find the sum of several consecutive numbers is to directly find the prefix sum, which can quickly find the sum of these numbers. Therefore, only maintenance is required m m The minimum of m prefixes and is subtracted from the current prefix and to get the answer.

code

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;

const int LEN = 5e5 + 115;
LL arr[LEN] = {0, }, x, n, m, ans = 0;
deque<LL>deq;  // The deque of stl is used here

int main(){
#ifndef ONLINE_JUDGE
    freopen("in.in", "r", stdin);
#endif
    std::ios::sync_with_stdio(false);
    cin >> n >> m;
    for (int i = 1; i <= n; ++ i){
        cin >> x;
        arr[i] = x + arr[i - 1];
    }
    deq.push_back(0);  // If you don't eat cake, it will naturally be 0
    for (int i = 1; i <= n; ++ i){
        while (deq.front() + m < i){  // Ensure that the minimum value is in the range of i-m
            deq.pop_front();
        }
        ans = max(ans, arr[i] - arr[deq.front()]);  // Get the best answer
        while (!deq.empty() && arr[deq.back()] >= arr[i]){  // When joining the team, we need to maintain its monotony
            deq.pop_back();
        }
        deq.push_back(i);
    }
    cout << ans << endl;
    return 0;
}

Example: Luogu P1725

This is a linear dp, and each step can only start from i i i to [ i + l , i + r ] [i+l, i+r] The grid of [i+l,i+r], then the price of this position must be from the grid that can come [ i − r , i + l ] [i-r, i+l] Transfer from [i − r,i+l], and the dynamic gauge equation is f ( i ) = m a x { f ( j )   ∣   i − r ≤ j ≤ i − l } + c o s t ( i ) f(i) = max\left\{ f(j) \, | \, i - r \le j \le i - l \right\} + cost(i) f(i)=max{f(j) ∣ i − r ≤ j ≤ i − l}+cost(i), so you can write such a code.

#include <bits/stdc++.h>
#define endn "\n"
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;

const int LEN = 1e4 + 10;
LL dp[LEN] = {0, }, n, l, r, x, arr[LEN] = {0, }, ans = -0x7FFFFFFFF;

void solve(){
    cin >> n >> l >> r;
    for (int i = 0; i <= n; ++ i){
        cin >> arr[i];
    }
    for (int i = l; i <= n; ++ i){
        for (int j = i - l; j >= 0 && j >= i - r; -- j){
            dp[i] = max(dp[i], dp[j] + arr[i]);
        }
        if (i + r >= n){
            ans = max(ans, dp[i]);
        }
    }
    cout << ans << endn;
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("in.in", "r", stdin);
#endif
    std::ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    solve();
    return 0;
}

Then you found TLE

But in fact, just look at the enumeration in the above code d p [ j ] + a r r [ i ] dp[j] + arr[i] dp[j]+arr[i], if you can directly find the largest d p [ j ] dp[j] dp[j] isn't it better. Take a closer look. This enumeration is to find the maximum value in the interval, so it's OK to use monotone queue enumeration directly.

Optimizing dp using monotone queue

#include <bits/stdc++.h>
#define endn "\n"
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;

const int LEN = 1e4 + 10;
LL dp[LEN] = {0, }, n, l, r, x, arr[LEN] = {0, }, ans = -0x7FFFFFFFF;
deque<LL>deq;

void solve(){
    cin >> n >> l >> r;
    for (int i = 0; i <= n; ++ i){
        cin >> arr[i];
    }
    for (int i = l; i <= n; ++ i){
        while (!deq.empty() && dp[deq.back()] < dp[i - l]){
            deq.pop_front();
        }
        deq.push_back(i - l);
        while (deq.front() < i - r){
            deq.pop_front();
        }
        dp[i] = dp[deq.front()] + arr[i];
        ans = max(ans, dp[i]);
    }
    cout << ans << endn;
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("in.in", "r", stdin);
#endif
    std::ios::sync_with_stdio(false);
    cin.tie(0), cout.tie(0);
    solve();
    return 0;
}

Topics: C++ data structure