Efficient Subarray Range Sum Calculation Using Monotonic Stacks

The objective is to evaluate the following double summation for a sequence $A$ of length $N$: $$ \sum_{L=0}^{N-1} \sum_{R=L}^{N-1} \left( \max_{k \in [L, R]} A[k] - \min_{k \in [L, R]} A[k] \right) $$ A brute-force enumeration of all contiguous segments results in quadratic or cubic complexity, which is insufficient for large inputs. Two linear-time strategies utilizing monotonic stacks can resolve this efficiently.

Strategy 1: Incremental Dynamic Programming

Rather than iterating over subarray boundaries, track the cumulative maximum and minimum values for all subarrays terminating at index $i$. Define $DP_{max}[i]$ as the sum of maximums for subarrays $A[0..i], A[1..i], \dots, A[i..i]$. Similarly, define $DP_{min}[i]$ for minimums. The final result becomes $\sum_{i=0}^{N-1} (DP_{max}[i] - DP_{min}[i])$.

To derive $DP_{max}[i]$, identify the nearest index $P < i$ where $A[P] > A[i]$. For any starting position $k$ satisfying $P < k \le i$, the maximum of $A[k..i]$ is strictly $A[i]$. For $k \le P$, the maximum value matches that of the subarray ending at $P$. This observation yields the recurrence: $$ DP_{max}[i] = DP_{max}[P] + A[i] \times (i - P) $$ If no such $P$ exists, $P$ defaults to $-1$, and the term $DP_{max}[P]$ vanishes. The index $P$ is retrieved in amortized constant time by maintaining a monotonically decreasing stack. The minimum accumulation $DP_{min}[i]$ follows an identical pattern using a monotonically increasing stack. Both passes execute in $O(N)$ time.

#include <iostream>
#include <vector>
#include <stack>

using ll = long long;

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int n;
    if (!(std::cin >> n)) return 0;

    std::vector<int> arr(n);
    for (int &val : arr) std::cin >> val;

    std::vector<ll> max_suffix_sum(n), min_suffix_sum(n);
    std::stack<int> dec_stk, inc_stk;

    for (int i = 0; i < n; ++i) {
        while (!dec_stk.empty() && arr[dec_stk.top()] <= arr[i]) {
            dec_stk.pop();
        }
        int prev_max_idx = dec_stk.empty() ? -1 : dec_stk.top();
        max_suffix_sum[i] = (prev_max_idx == -1 ? 0 : max_suffix_sum[prev_max_idx]) +
                            static_cast<ll>(arr[i]) * (i - prev_max_idx);
        dec_stk.push(i);

        while (!inc_stk.empty() && arr[inc_stk.top()] >= arr[i]) {
            inc_stk.pop();
        }
        int prev_min_idx = inc_stk.empty() ? -1 : inc_stk.top();
        min_suffix_sum[i] = (prev_min_idx == -1 ? 0 : min_suffix_sum[prev_min_idx]) +
                            static_cast<ll>(arr[i]) * (i - prev_min_idx);
        inc_stk.push(i);
    }

    ll total_diff = 0;
    for (int i = 0; i < n; ++i) {
        total_diff += max_suffix_sum[i] - min_suffix_sum[i];
    }

    std::cout << total_diff << '\n';
    return 0;
}

Strategy 2: Element Contribution Analysis

The original expression separates into independent maximum and minimum summations: $$ \sum_{L, R} \max(A[L..R]) - \sum_{L, R} \min(A[L..R]) $$ Focus on the maximum component. Determine how many contiguous subarrays designate $A[i]$ as their maximum element. Define $Left[i]$ as the closest index to the left where $A[Left[i]] > A[i]$, and $Right[i]$ as the closest index to the right where $A[Right[i]] \ge A[i]$. The asymmetric comparison operators prevent duplicate counting when equal values appear. Within the exclusive range $(Left[i], Right[i])$, $A[i]$ dominates. The number of valid subarrays is $(i - Left[i]) \times (Right[i] - i)$.

The total contribution of $A[i]$ to the maximum sum is $A[i] \times (i - Left[i]) \times (Right[i] - i)$. The minimum sum applies symmetric boundary conditions ($<$ on the left, $\le$ on the right). Precomputing all boundaries requires four linear scans with monotonic stacks, maintaining an overall $O(N)$ complexity.

#include <iostream>
#include <vector>
#include <stack>

using ll = long long;

std::vector<int> compute_left_limits(const std::vector<int>& data, bool tracking_max) {
    int n = data.size();
    std::vector<int> limits(n, -1);
    std::stack<int> stk;
    for (int i = 0; i < n; ++i) {
        while (!stk.empty() && (tracking_max ? data[stk.top()] <= data[i] : data[stk.top()] >= data[i])) {
            stk.pop();
        }
        limits[i] = stk.empty() ? -1 : stk.top();
        stk.push(i);
    }
    return limits;
}

std::vector<int> compute_right_limits(const std::vector<int>& data, bool tracking_max) {
    int n = data.size();
    std::vector<int> limits(n, n);
    std::stack<int> stk;
    for (int i = n - 1; i >= 0; --i) {
        while (!stk.empty() && (tracking_max ? data[stk.top()] < data[i] : data[stk.top()] > data[i])) {
            stk.pop();
        }
        limits[i] = stk.empty() ? n : stk.top();
        stk.push(i);
    }
    return limits;
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);

    int n;
    if (!(std::cin >> n)) return 0;

    std::vector<int> seq(n);
    for (int &x : seq) std::cin >> x;

    auto max_L = compute_left_limits(seq, true);
    auto max_R = compute_right_limits(seq, true);
    auto min_L = compute_left_limits(seq, false);
    auto min_R = compute_right_limits(seq, false);

    ll result = 0;
    for (int i = 0; i < n; ++i) {
        ll max_occurrences = static_cast<ll>(i - max_L[i]) * (max_R[i] - i);
        ll min_occurrences = static_cast<ll>(i - min_L[i]) * (min_R[i] - i);
        result += seq[i] * (max_occurrences - min_occurrences);
    }

    std::cout << result << '\n';
    return 0;
}

Tags: algorithms monotonic-stack dynamic-programming competitive-programming cpp

Posted on Sun, 09 Aug 2026 16:35:03 +0000 by jhlove