区区区间间间
题目描述
给出长度为n的序列a,其中第i个元素为ai ,定义区间(l,r)的价值为v(l,r) = max(ai - aj | l<=i,j<=r)
请你计算出∑ (l=1,n) ∑ (r=l+1,n) v(l,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.
备注:
5T⩽20 , n⩽10^5 , 0⩽ai ⩽10^5
不保证数据随机生成!
分析:
将问题转化成求每一个长度大于1的子区间的最大值之和 - 每一个长度大于1的子区间的最小值。即当a[ i ]为最大值时,a[ i ]可以控制的最长区间,然后算这个区间有几个子区间,每个子区间都能提供a[ i ]的贡献,而找a[ i ]控制的最长区间就要分别找a[ i ]左右第一个大于a[ i ]的元素的下标,
AC代码:
代码如下:
#include<iostream>
using namespace std;
typedef long long ll;
ll n,T,a[100005],q[100005];
ll solve()
{
ll r[100005]={0},l[100005]={0};
ll sum=0;
for(int i=1;i<=n;++i){
int j=i-1;
while(j>0&&a[j]<=a[i]) //找左边第一个大于a[j]的元素的下标
j=l[j];
l[i]=j;
}
for(int i=n;i>=1;--i){
int j=i+1;
while(j<=n&&a[i]>a[j])
j=r[j];
r[i]=j;
}
for(int i=1;i<=n;++i){
sum+=a[i]*(r[i]-l[i]-2);// 当a[i]为端点,区间个数本质也就是 (r[i]-i-1)+(i-l[i]-1)
sum+=a[i]*(i-l[i]-1)*(r[i]-i-1); //当a[i]不是端点,区间个数为左边点的个数乘右边点的个数
}
return sum;
}
int main()
{
cin>>T;
for(int i=0;i<T;++i){
scanf("%lld",&n);
for(int j=1;j<=n;++j)
scanf("%lld",a+j);
ll ans=0;
ans+=solve();
for(int i=1;i<=n;++i)
a[i]=-a[i]; //取反再求一次最大值
ans+=solve(); //所以这里不是ans-=solve()
printf("%lld\n",ans);
}
}
总结
单调栈