题目大意
题面
让我们求一个序列中的
\[\sum^{n}_{i=1}\sum^{n}_{j=i}(\max_{i\leq k\leq j} a_k-\min_{i\leq k \leq j} a_k)
\]
Sol
由于暴力是\(O(n^2)\)的,所以我们需要优化
我们先看暴力的流程:
- 每次选取一段区间求出其中的最大最小值(可以使用滑动窗口控制右端点)
- 随后计算答案
复杂度瓶颈主要在于对于每一个右端点都要处理一次
我们把原式拆开:
\[\sum^{n}_{i=1}\sum^{n}_{j=i}\max_{i\leq k\leq j} a_k-\sum^{n}_{i=1}\sum^{n}_{j=i}\min_{i\leq k \leq j} a_k
\]
看起来求最大值和最小值的方法是一致的,所以我们先考虑求最大值
对于每一个\(a_i\),我们一定能找到一个最大的
区间\([l,r]\)满足\(\max_{l\leq k\leq r} a_k=a_i\)
这个时候就存在\((i-l+1)(r-i+1)\)个区间最大值为\(a_i\) (可以参考左边选几个 右边选几个理解,左右可以不选)
如果要直接维护一个数的区间,复杂度依然不对
转换一下,既然这个区间是最大的
,那么说明\(a_{l-1}\)和\(a_{r+1}\)一定是大于等于\(a_i\)的 (至于为什么大于等于待会说)
这个时候就变成了单调栈的模板,维护一个数左边第一个比他大的数的位置
上面个区间个数就可以变成\((i-L)(R-i)\) ,其中\(L\)为左边第一个大于它的数,\(R\)为右边第一个大于等于它的数
一边取等于一边不取是因为不会产生重复贡献 (考虑一下 \(1,0,1,0,1\)的情况)
(也可以左边取右边不取)
最小值同理,整体复杂度是\(O(n)\),可以AC
Code
#include <cstdio>
#include <iostream>
#include <algorithm>using namespace std;typedef long long LL;const int N = 3e5+10;int n;
LL a[N];
LL ss[N] , hh , tt;
LL lmax[N] , rmax[N] , lmin[N] , rmin[N];int main() {scanf("%d" , &n);for(int i = 1 ; i <= n ; i ++)scanf("%lld" , &a[i]);hh = 0; tt = -1;for(int i = 1 ; i <= n ; i ++) {while(hh <= tt && a[ss[tt]] <= a[i]) tt --;if(hh <= tt) lmax[i] = ss[tt];ss[++ tt] = i;}hh = 0; tt = -1;for(int i = 1 ; i <= n ; i ++) {while(hh <= tt && a[ss[tt]] >= a[i]) tt --;if(hh <= tt) lmin[i] = ss[tt];ss[++ tt] = i;}hh = 0; tt = -1;for(int i = n ; i >= 1 ; i --) {while(hh <= tt && a[ss[tt]] < a[i]) tt --;if(hh <= tt) rmax[i] = ss[tt];else rmax[i] = n+1;ss[++ tt] = i;}hh = 0; tt = -1;for(int i = n ; i >= 1 ; i --) {while(hh <= tt && a[ss[tt]] > a[i]) tt --;if(hh <= tt) rmin[i] = ss[tt];else rmin[i] = n+1;ss[++ tt] = i;}LL res = 0;for(int i = 1 ; i <= n ; i ++) {res += a[i] * (i - lmax[i]) * (rmax[i] - i);res -= a[i] * (i - lmin[i]) * (rmin[i] - i);}printf("%lld\n" , res);return 0;
}