题干:
N个整数组成的数组,定义子数组aii..ajj的宽度为:max(ai..aj) - min(ai..aj),求所有子数组的宽度和。
Input
第1行:1个数N,表示数组的长度。(1 <= N <= 50000)
第2 - N + 1行:每行1个数,表示数组中的元素(1 <= Aii <= 50000)
Output
输出所有子数组的宽度和。
Sample Input
5 1 2 3 4 5
Sample Output
20
解题报告:
这题显然不能枚举区间然后分别计算,所以很多技巧根本不需要考虑(比如排序一下?)
然后我们考虑枚举每一个元素,计算他对答案的贡献。
记得去重,也就是,一边算 严格单调,一边是 不严格单调,这样就可以保证不重不漏。(像 5 1 2 1 3 3 这样的样例,算贡献时(2,4)这种区间 只需要算一次。所以单调栈的时候一边是严格单调,一边是不严格单调)这个去重的方法很巧妙啊、
AC代码:
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int MAX = 50000 + 5;
ll a[MAX],maxx[MAX],minn[MAX];
int l[MAX],r[MAX],L[MAX],R[MAX];//左侧第一个比我小的,右侧第一个比我小的。
stack<int> sk;
int main()
{
int n;
cin>>n;
for(int i = 1; i<=n; i++) scanf("%lld",a+i);
//从左到右找第一个比我小的,所以从左到右维护一个单调递增栈。
for(int i = 1; i<=n; i++) {
while(!sk.empty() && a[sk.top()] > a[i]) sk.pop();
if(sk.size()) l[i] = sk.top();
else l[i] = 0;
sk.push(i);
// printf("%d %d\n",i,l[i]);
}
while(!sk.empty()) sk.pop();
//从右到左找第一个比我小的,所以维护从右向左维护一个单调递减栈。
for(int i = n; i>=1; i--) {
while(!sk.empty() && a[sk.top()] >= a[i]) sk.pop();
if(sk.size()) r[i] = sk.top();
else r[i] = n+1;
sk.push(i);
// printf("%d %d\n",i,r[i]);
}
for(int i = 1; i<=n; i++) minn[i] = (r[i] - i - 1) * (i-l[i]-1) + (r[i]-l[i]-1);
while(!sk.empty()) sk.pop();
//从左到右找第一个比我大的,所以从左向右维护一个单调递减栈,
for(int i = 1; i<=n; i++) {
while(!sk.empty() && a[sk.top()] < a[i]) sk.pop();
if(sk.size()) L[i] = sk.top();
else L[i] = 0;
sk.push(i);
}
while(!sk.empty()) sk.pop();
for(int i = n; i>=1; i--) {
while(!sk.empty() && a[sk.top()] <= a[i]) sk.pop();
if(sk.size()) R[i] = sk.top();
else R[i] = n+1;
sk.push(i);
}
for(int i = 1; i<=n; i++) maxx[i] = (R[i] - i-1) * (i-L[i]-1) + (R[i]-L[i]-1);
// printf("yingyingying\n");
// for(int i = 1; i<=n; i++) {
// printf("%lld %lld\n",minn[i],maxx[i]);
// }
ll ans = 0;
for(int i = 1; i<=n; i++) {
ans += a[i] * (maxx[i] - minn[i]);
}
printf("%lld\n",ans);
return 0;
}
还有几个好的题解:(暂时还未看)
http://www.itdaan.com/blog/2017/09/18/fb224770ef3801dcce5fcac76c36b4df.html
http://www.itdaan.com/blog/2017/08/22/df09138e3e52fc862172e8c8875d95ff.html