给定数组a,求数组a的所有子串的最大值,对这些最大值求和。
链接:子串的最大差
思路:
直接枚举左端点不会做。考虑一个数对答案的贡献,如果当前的数是a[i],寻找左边第一个大于他的数的pos1,右边第一个大于等于(后面解释为什么是大于等于)他的数pos2,那么在区间[pos1 + 1, pos2 - 1]中,包含 i 的区间最大值就都是a[i]。
nlogn做法:从大到小向set中加入 >= a[i] 的数的下标,然后在set中二分找离i最近的数。
o(n)做法:单调栈寻找左边第一个大于a[i]的数。维护一个单调递减的栈,从右到左遍历数组a,如果当前元素小于栈顶,则入栈;反之弹出栈顶,并且记录栈顶元素 x 的左边第一个大于他的数的下标是 i。
为什么右边是大于等于:
看这个样例:
5
6 4 6 2 6
对于第一个1,以他为最大值的区间有5个;对于第二个6,如果右边的限制是大于,那么以他为最大值的区间也就有5个了,这样子就产生了重复。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 5, mod = 1e9 + 7;
int a[N];
struct node {
int val, pos;
};
stack<node> q;
int ma_l[N], ma_r[N];
int mi_l[N], mi_r[N];
signed main ()
{
// freopen("data.in","r",stdin);
// freopen("data.out","w",stdout);
int n;
cin >> n;
for (int i = 1; i <= n; i++) scanf("%lld", &a[i]);
// 单调减的栈可以维护一个数左右比他大,反之则左右第一个比他小
q.emplace(node{10000000000, n + 1});
for (int i = n; i >= 1; i--) {
while(a[i] >= q.top().val){
int pos = q.top().pos;
q.pop();
ma_l[pos] = i;
}
q.emplace(node{a[i], i});
}
while(!q.empty()) q.pop();
q.emplace(node{10000000000, 0});
for (int i = 1; i <= n; i++){
while(a[i] > q.top().val){
int pos = q.top().pos;
q.pop();
ma_r[pos] = i;
}
q.emplace(node{a[i], i});
}
q.emplace(node{-10000000000, n + 1});
for (int i = n; i >= 1; i--) {
while(a[i] <= q.top().val){
int pos = q.top().pos;
q.pop();
mi_l[pos] = i;
}
q.emplace(node{a[i], i});
}
while(!q.empty()) q.pop();
q.emplace(node{-10000000000, 0});
for (int i = 1; i <= n; i++){
while(a[i] < q.top().val){
int pos = q.top().pos;
q.pop();
mi_r[pos] = i;
}
q.emplace(node{a[i], i});
}
// for (int i = 1; i <= n; i++){
// cout << mi_l[i] << " ";
// }
// cout << endl;
// for (int i = 1; i <= n; i++){
// cout << mi_r[i] << " ";
// }
// cout << endl;
int sum_ma = 0, sum_mi = 0;
for (int i = 1; i <= n; i++){
int l = ma_l[i], r = ma_r[i];
if (l == 0) l = 1;
else l++;
if (r == 0) r = n;
else r--;
sum_ma += (i - l + 1) * (r - i + 1) * a[i];
l = mi_l[i], r = mi_r[i];
if (l == 0) l = 1;
else l++;
if (r == 0) r = n;
else r--;
sum_mi += (i - l + 1) * (r - i + 1) * a[i];
}
// cout << sum_ma << " " << sum_mi << endl;
cout << sum_ma - sum_mi;
return 0;
}