【区区区间间间——单调栈】

区区区间间间


题目描述

给出长度为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);
    }
}

总结

单调栈

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值