区区区间间间(单调栈)
区区区间间间
https://ac.nowcoder.com/acm/problem/20806
题目描述
给出长度为n的序列a,其中第i个元素为aia_iai,定义区间(l,r)的价值为
vl,r=max(ai−aj∣l⩽i,j⩽r)v_{l,r} = max(a_i - a_j | l \leqslant i,j\leqslant r)vl,r=max(ai−aj∣l⩽i,j⩽r)
请你计算出∑l=1n∑r=l+1nvl,r\sum_{l = 1}^n \sum_{r = l + 1}^n v_{l,r}∑l=1n∑r=l+1nvl,r
输入描述:
第一行输入数据组数T 对于每组数据,第一行为一个整数n,表示序列长度 接下来一行有n个数,表示序列内的元素
输出描述:
对于每组数据,输出一个整数表示答案
示例1
输入
3 3 4 2 3 5 1 8 4 3 9 20 2 8 15 1 10 5 19 19 3 5 6 6 2 8 2 12 16 3 8 17
输出
5 57 2712
说明
对于一组测试数据的解释: 区间[1, 2]的贡献为:4 - 2 = 2 区间[1, 3]的贡献为:4 - 2 = 2 区间[2, 3]的贡献为:3 - 2 = 1 2 + 1 + 2 = 5.
备注:
T⩽20,n⩽105,0⩽ai⩽105T \leqslant 20,n\leqslant 10^5, 0 \leqslant a_i \leqslant 10^5T⩽20,n⩽105,0⩽ai⩽105不保证数据随机生成!
思路
用单调栈O(n)进行维护。
先化简,将连加内的减号打开,求所有的子区间的最大值之和,最小值之和。
两遍单调栈计算当前元素作为最大值的时候的区间左右端点
求最小值的时候就把当前元素乘上-1,这样继续用求最大值的函数,刚好有了负号。
最后ans根据不同情况求和就行了。
注意开long long ans会爆int,在计算的过程中可能也会爆int,可以强转ll。
代码
//区区区间间间(单调栈) #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> using namespace std; typedef long long ll; const int N = 1e5 + 10; int n; int a[N] , l[N] , r[N]; ll work() { for(int i = 1 ; i <= n ; i++) { int j = i; while(j > 1 && a[j - 1] <= a[i]) j = l[j - 1]; l[i] = j; } for(int i = n ; i ; i--) { int j = i; while(j < n && a[j + 1] < a[i]) j = r[j + 1]; r[i] = j; } ll ans = 0; for(int i = 1 ; i <= n ; i++) { ans += (ll)a[i] * (r[i] - l[i]); ans += (ll)a[i] * (i - l[i]) * (r[i] - i); } return ans; } int main() { int t; scanf("%d" , &t); while(t--) { scanf("%d" , &n); for(int i = 1 ; i <= n ; i++) scanf("%d" , &a[i]); ll ans = work(); for(int i = 1 ; i <= n ; i++) a[i] *= -1; ans += work(); printf("%lld\n" , ans); } return 0; }
牛客算法竞赛入门课第二节习题题解 文章被收录于专栏
入门课第二节习题题解