N个整数组成的数组,定义子数组a[i]..a[j]的宽度为:max(a[i]..a[j]) - min(a[i]..a[j]),求所有子数组的宽度和。
Input
第1行:1个数N,表示数组的长度。(1 <= N <= 50000) 第2 - N + 1行:每行1个数,表示数组中的元素(1 <= A[i] <= 50000)
Output
输出所有子数组的宽度和。
Input示例
5 1 2 3 4 5
Output示例
20
题解:
这一题可以向上一篇博客一样做
首先,可以把题目转换为求每个子区间的最大值和sum1减去每个子区间最小值和sum2。
先维护一个单调增队列,可以求出以每个元素为区间最小值的向前延伸pre值和向后延伸next值。
然后维护一个单调减队列,可以求出以每个元素为区间最大值的向前延伸pre值和向后延伸next值。
然后对于每个数,通过pre和next值求出他在sum1/sum2中出现的次数(次数就是pre*next)。
#include <iostream>
#include<cstdio>
#include<algorithm>
#include<stack>
using namespace std;
const int maxn=1e5+100;
typedef long long ll;
struct node{
ll num;
int pre,next,p;
node(ll a=0,int d=0):num(a),pre(1),next(1),p(d) {}
};
ll sum[maxn];
ll a[maxn];
int main(int argc, const char * argv[]) {
int n;
scanf("%d",&n);
sum[0]=0;
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
stack<node> s;
node tmp=node(a[1],1);
s.push(tmp);
ll ans=0,ans1=0;
for(int i=2;i<=n;i++)
{
node t=node(a[i],i);
while(!s.empty()&&s.top().num>=t.num)
{
node t1=s.top();
s.pop();
t.pre+=t1.pre;
if(!s.empty())
s.top().next+=t1.next;
ans+=t1.num*(t1.next*t1.pre);
}
s.push(t);
}
while(!s.empty())
{
node t1=s.top();
s.pop();
if(!s.empty())
s.top().next+=t1.next;
ans+=t1.num*(t1.next*t1.pre);
}
node tt=node(a[1],1);
s.push(tt);
for(int i=2;i<=n;i++)
{
node t=node(a[i],i);
while(!s.empty()&&t.num>=s.top().num)
{
node t1=s.top();
s.pop();
if(!s.empty())
s.top().next+=t1.next;
t.pre+=t1.pre;
ans1+=t1.num*(t1.pre*t1.next);
}
s.push(t);
}
while(!s.empty())
{
node t1=s.top();
s.pop();
if(!s.empty())
s.top().next+=t1.next;
ans1+=t1.num*(t1.pre*t1.next);
}
printf("%lld\n",ans1-ans);
return 0;
}
后来,在近一步深刻理解单调栈的原理后,我又学会了另一种写法:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
#include<stack>
using namespace std;
#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
#define pb push_back
#define fi first
#define se second
typedef vector<int> VI;
typedef long long ll;
typedef pair<int,int> PII;
const int maxn=50000+10;
int a[maxn],s[maxn]; //此处栈中记录的是数组的下标
int main()
{
int n;
scanf("%d",&n);
rep(i,1,n+1) scanf("%d",&a[i]);
a[++n]=0; //为了能够最后清空栈内元素,所以对队尾部赋值一个比任何元素都小的值,比如对于例子1 2 3 4这样的
ll ans1=0,ans2=0;
int top=0;
rep(i,1,n+1)
{
while(top&&a[i]<a[s[top]]) //单调增栈
{
ans1+=1ll*(s[top]-s[top-1])*(i-s[top])*a[s[top]];//[s[top-1]+1,i-1]的最小值就是a[s[top]]
top--;
}
s[++top]=i;
}
a[n]=maxn; //这个的原理同上
top=0;
rep(i,1,n+1)
{
while(top&&a[i]>a[s[top]]) //单调减栈
{
ans2+=1ll*(s[top]-s[top-1])*(i-s[top])*a[s[top]];
top--;
}
s[++top]=i;
}
printf("%lld\n",ans2-ans1);
return 0;
}