(牛客)区区区间间间(单调栈)

区区区间间间

思路

定义(V_{l, r} = max(a_i - a _j | l <= i, j <= r))

(sum_{l = 1} ^ {n} sum_{r = l + 1} ^ {n}V_{l, r}),转换(sum_{l = 1} ^{n} sum_{r = l + 1} ^ {n}max(a_i) - sum_{l = 1} ^{n} sum_{r = l + 1} ^ {n}min(a_i))

因此这里就转换成了求一个区间内的最大和最小值。

我们考虑最大值的区间覆盖,这里就形成了一个单调栈了,在当前取值的左右两侧都是递增的,因此我们得到当前值得区间覆盖,只需要左右分别进行两趟扫描,就可以得到其最左和最右的区间覆盖,分别记为(l[i],r[i])

在这里我们还需要考虑的一点就是当存在相同的时候, 我们要选择最左还是最右来当一个最大值,这样才能保证多值相同存在的情况下我们得到的区间的合理性。这里我选择左侧的相同值更大。

再来说一下,答案的更新,首先我们得到区间的覆盖(l[i],r[i])

我们选定i作为区间的一个端点,可以得到((i - l[i]) + (r[i] - i) = r[i] - l[i])个区间,我们假定i在区间里,总共的区间就是((r[i] - i) * (i - l[i])),左右各取端点的排列嘛。

然后我们需要更新的答案就是(a[i] * (r[i] - l[i] + (r[i] - i) * (i - l[i])))

代码

#include <bits/stdc++.h>
 
using namespace std;
 
typedef long long ll;
 
const int N = 1e5 + 10;
 
int a[N], n;
 
ll l[N], r[N];
 
ll get_max() {
    ll ans = 0;
    stack<int> stk;
    for(int i = 1; i <= n; i++) {
        while(!stk.empty() && a[i] >= a[stk.top()]) stk.pop();//取等号,
        if(stk.size() == 0) l[i] = 1;//栈空代表他的覆盖区域是最左点,
        else    l[i] = stk.top() + 1;
        stk.push(i);
    }
    while(!stk.empty()) stk.pop();
    for(int i = n; i >= 0; i--) {
        while(!stk.empty() && a[i] > a[stk.top()])  stk.pop();//不取等号,
        if(stk.size() == 0) r[i] = n;
        else r[i] = stk.top() - 1;
        stk.push(i);
    }
    for(int i = 1; i <= n; i++)
        ans += 1ll * (r[i] - l[i] + (i - l[i]) * (r[i] - i)) * a[i];
    return ans;
}
 
int main() {
    // freopen("in.txt", "r", stdin);
    int t; scanf("%d", &t);
    while(t--) {
        scanf("%d", &n);
        for(int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        ll ans = 0;
        ans += get_max();//找到最大值,
        // printf("%lld
", ans);
        for(int i = 1; i <= n; i++)//负数的最大,也就是未改变值时的最小,只要加上这个值就是最小值了。
            a[i] = -a[i];
        ans += get_max();
        printf("%lld
", ans);
    }
    return 0;
}
原文地址:https://www.cnblogs.com/lifehappy/p/12922225.html