求所有子串的最大值之和

给定数组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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值