-
题目描述
-
输入描述:
第一行输入数据组数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.
备注:
-
分析
若用双重循环肯定会超时
用单调栈 的思想
对于单调递增栈,若当前进栈元素为 e,从栈顶开始遍历元素,把小于 e 或者等于 e 的元素弹出栈,直接遇到一个大于 e 的元素或者栈为空为止,然后再把 e 压入栈中。
对于单调递减栈,则每次弹出的是大于 e 或者等于 e 的元素。
题目中那个公式可以看成是每个区间的最大值减去每个区间最小值的和
先考虑每个区间的最大值,我们可以先考虑一个元素,计算若以该元素为区间的最大值,那这个区间最左边能到哪,最右边又能到哪。因为是该元素为最大值,我们只要知道以该元素为最大值的区间有多少个相乘就可以了。(把思考区间最大值最小值转换为思考每个元素作为最大值最小值的区间)
总区间有两种情况:
1.该元素为端点
2.该元素不为端点
遍历每个元素作为最大值的情况
再考虑每个区间的最小值,只需把元素值全乘-1再按求区间最大值的方法就行了,因为变为负数后求最大值就是原来正数时的最小值,只不过多了一个负号,这个带负号的值直接加每个区间最大值的和就是答案,因为原来是最大值的和减最小值的和,现在得到的最小值带了负号就直接加
最复杂的地方就是找当a[i]为区间最大值时最左边能到哪,最右边能到哪,这是一个不断迭代的过程
#include<iostream>
using namespace std;
const int N = 1e5 + 5;
typedef long long ll;
ll a[N];
ll l[N];
ll r[N];
int t, n;
ll solve() {
int i, j;
for (i = 1; i <= n; ++i) {
j = i;
while (j > 1 && a[j - 1] <= a[i])//a[i]大于等于a[j-1] 找以下标j-1为最大值 左边能延伸到的地方,
//那以下标i为最大值,左边也能延伸到那
j = l[j-1];//以下标j-1为最大值 左边能延伸到的 地方赋给j(更新j的值
l[i] = j;
}
for (i = n; i>=1; --i) {
j = i;
while (j < n && a[j + 1] < a[i])//这里不能用等号 会重复(?)
j = r[j + 1];
r[i] = j;
}
ll ans = 0;
for (i = 1; i <= n; ++i) {
ans += a[i] * (r[i] - l[i]);//以a[i]为端点 a[i]为最大值
ans += a[i] * (i - l[i])*(r[i] - i);//a[i]不为端点
}
return ans;
}
int main() {
cin >> t;
while (t--) {
cin >> n;
for (int i = 1; i <= n; ++i)cin >> a[i];
ll ans = solve();
for (int i = 1; i <= n; ++i) a[i] = -a[i];
cout << ans + solve() << endl;
}
system("pause");
return 0;
}
补充
for(int i=n;i>0;--i){
int j=i;
while(j<n&&a[j+1]<a[i])//j<n是为了让j+1<=n
//a[j+1]<a[i]不取等号因为前面l[]的取了等号,这里再取等号有的区间就会有两个最大值,最后答案会重复
//比如123321 以第一个3为最大值的区间就是整个从左到右,
//如果这里取了等号,那第二个3为最大值的区间也是整个区间
//这一个区间的计算会重复
j=r[j+1];
r[i]=j;//如果a[i]右边第一个数比它本身大,那它的右边界就是自己
}