子串的最大差 - 题目 - Daimayuan Online Judge
枚举每个子串不现实,肯定会超时
子串1的最大值-子串1的最小值+子串2的最大值-子串2的最小值+...
即为(子串1的最大值+子串2的最大值+...)-(子串1的最小值+子串2的最小值+...)
可以从贡献度的角度考虑,对于每一个数,求它作为最大值的贡献度和作为最小值的贡献度,所有最大值的贡献度减去所有最小值的贡献度即所求答案
求作为最大值的贡献度,对于某个数,求出它左边比它大的第一个数和右边比它大的第一个数,
例如5 2 2 3 3 4,那么对于第一个3,就是2 2 3 3,它作为最大值的贡献度就是从该数开始(包括它自己)往左数的个数乘从该数开始(包括它自己)往右数的个数(乘法原理),这样算出来的就是以该数作为最大值的包含该值的子串的个数
求作为最小值得贡献度同理
而求一个序列中每个数右边(左边)比它大(小)的第一个数可以用单调栈(具体解释见算法:单调栈-CSDN博客)
但是要注意,以求最大为例,在求左边第一个最大和右边第一个最大时,如果有相同的元素的话,是不能两边同时都取等号的,比如4 3 3 3,对于下标为2的3,左边第一个最大下标为1,如果右边第一个下标最大设为5,那么整个满足的子串就是3 3 3,黄色的3对该子串作了贡献
4 3 3 3中下标为3的3,满足的子串是3 3 3,黄色的3对该子串作了贡献,这样就重复了,因为3 3 3是一个子串,它的最大值3对该子串作了贡献,3 3 3这个子串的最大值是3,一个子串只有一个最大值对其做贡献
所以4 3 3 3中我们一边取等号,一边不取等号,对于下标为2的3,满足的子串是3,对于下标为3的3,满足的子串是3 3,对于下标为4的3,满足的子串是3 3 3,这样就能保证同一个子串只记录一次贡献
AC代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stack>
using namespace std;
typedef long long LL;
const int N = 5e5 + 10;
int a[N];
int n;
int lmax[N], lmin[N];//左边第一个最大的数的下标和第一个最小的数的下标
int rmax[N], rmin[N];//右边第一个最大的数的下标和第一个最小的数的下标
LL res;
int main()
{
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
stack<int>q;
//栈里面放下标,方便个数的计算
for (int i = 1; i <= n; i++) {
while (q.size() && a[q.top()] <= a[i]) q.pop();
if (!q.size()) lmax[i] = 0;
else lmax[i] = q.top();
q.push(i);
}
while (q.size()) q.pop();
for (int i = 1; i <= n; i++) {
while (q.size() && a[q.top()] >= a[i]) q.pop();
if (!q.size()) lmin[i] = 0;
else lmin[i] = q.top();
q.push(i);
}
while (q.size()) q.pop();
for (int i = n; i >= 1; i--) {
while (q.size() && a[q.top()] <a[i]) q.pop();
if (!q.size()) rmax[i] = n+1;
else rmax[i] = q.top();
q.push(i);
}
while (q.size()) q.pop();
for (int i = n; i >= 1; i--) {
while (q.size() && a[q.top()] >a[i]) q.pop();
if (!q.size()) rmin[i] = n+1;
else rmin[i] = q.top();
q.push(i);
}
for (int i = 1; i <= n; i++) {
res += (LL)a[i] * (rmax[i] - i) * (i - lmax[i]) - (LL)a[i] * (rmin[i] - i) * (i - lmin[i]);
}
cout<<res<<endl;
return 0;
}