(牛客)区区区间间间(单调栈)
区区区间间间
思路
定义\(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\n", ans);
for(int i = 1; i <= n; i++)//负数的最大,也就是未改变值时的最小,只要加上这个值就是最小值了。
a[i] = -a[i];
ans += get_max();
printf("%lld\n", ans);
}
return 0;
}