牛客 区区区间间间——单调栈+思维

9 篇文章 0 订阅

题目链接

题目

在这里插入图片描述

思路

一看数据范围是1e5就知道这道题大概率是个nlogn的做法,
我们这样考虑:

  • 对于任意一个数 a i a_i ai,它作为区间最大值(最小值)出现的次数,最后 a i a_i ai对答案的贡献就是 a i ∗ ( m a x [ i ] − m i n [ i ] ) a_i*(max[i]-min[i]) ai(max[i]min[i]),累加起来即可
  • 如何求 a i a_i ai作为最大值出现的次数呢?我们找到左边第一个比它大的数 a x a_x ax,右边第一个比它大的数 a y a_y ay, 那么在区间 [ x + 1 , y − 1 ] [x+1,y-1] [x+1,y1]范围内, a x a_x ax就是最大的,在这个区间内设左边比它小的数有 x x x个,右边有 y y y个,那么在这个大区间的 y + x ∗ ( y + 1 ) y+x*(y+1) y+x(y+1)个子区间内它都是最大值
  • 例如数据:1 2 8 4 3, 这个8前面有2个数比它小,右边有2个数比它小,对于前面2个数,比如对于1,[1,2,8]一次,[1,2,8,4]一次,[1,2,8,4,3]一次,共y+1=2+1=3次,共有x=2个数,所以 x ∗ ( y + 1 ) x*(y+1) x(y+1), 对于后面2个数[8,4]一次,[8,4,3]一次共 y = 2 y=2 y=2次,所以是y+x*(y+1)次
  • 如何求 a i a_i ai左边第一个比它大的数的位置呢,如何求 a i a_i ai右边第一个比它大的数的位置呢——单调栈,没学过单调栈的同学去自学一下
  • 既然能够求出 a i a_i ai作为最大值出现的次数,那么 a i a_i ai作为最小值同理即可
  • 注意事项:因为这道题数据可能出现多个值相同的元素,那么在写单调栈的时候就得注意,一个是a[stk[tt]]<=a[i],一个是a[stk[tt]]<a[i], 左边严格找到第一个比它小的数,右边找到一个小于等于它的数,否则会出现重复计算
  • 例如数据2 1 2, 很明显2只能被加3次,如果都是小于等于,那么对于每个位置的数,可以扩展的为(1,3), (2,2),(1,3),区间(1,3)被重复计算了两次,正确应该是(1,3),(2,2),(2,3)

参考代码

代码写得有点冗长,可以精简一下

#include<bits/stdc++.h>

using namespace std;
#define ll long long
#define mem(a,b) memset(a,b,sizeof a)
const int N = 1e5+9;
ll mx[N],mi[N];
ll l[N],r[N],a[N];
ll stk[N];
int tt;
int main()
{
	int t;
	cin>>t;
	while(t--)
	{
		int n;
		scanf("%d",&n);
		tt=0;
		mem(a,0);
		mem(stk,0);
		for(int i=1; i<=n; i++)
		{
			scanf("%lld",a+i);
			while(tt&&a[stk[tt]]>=a[i]) tt--;
			l[i]=stk[tt];
			stk[++tt]=i;
		}
		tt=0;
		stk[tt]=n+1;
		for(int i=n; i>=1; i--)
		{
			while(tt&&a[stk[tt]]>a[i]) tt--;
			r[i]=stk[tt];
			stk[++tt]=i;
			ll x=i-l[i]-1;//a[i]左边比它大的数的个数 ,这里表示连续的 
			ll y=r[i]-i-1; 
			mi[i]=y+x*(y+1);
			//cout<<mi[i]<<endl;
		   /// cout<<r[i]<<endl;	
		}
		tt=0;
		stk[tt]=0;
		for(int i=1; i<=n; i++)
		{
			while(tt&&a[stk[tt]]<=a[i]) tt--;
			l[i]=stk[tt];
		//	cout<<a[l[i]]<<endl;
			stk[++tt]=i;

		}
		tt=0;
		stk[tt]=n+1;
		for(int i=n; i>=1; i--)
		{
			while(tt&&a[stk[tt]]<a[i]) tt--;
			r[i]=stk[tt];
			stk[++tt]=i;
			ll x=i-l[i]-1;//a[i]左边比它大的数的个数 ,这里表示连续的 
			ll y=r[i]-i-1; 
			mx[i]=y+x*(y+1);
		//	cout<<r[i]<<endl;
		//	cout<<x<<" "<<y<<endl;
		   // cout<<mx[i]<<endl;	
		}
		ll ans=0;
		for(int i=1; i<=n; i++)
		{
			//cout<<mi[i]<<" "<<mx[i]<<endl;
			ans+=a[i]*(mx[i]-mi[i]);
		}
		printf("%lld\n",ans);
	}
	
	
	return 0;
 } 

优化版代码

我们可以发现,当 a i a_i ai作为最小值在答案中出现的次数等价于 − a i -a_i ai最为最大值出现的次数,所以我们求 a i a_i ai作为最小值对答案的贡献时,令a[i]=-a[i],跑一遍作为最大值的函数即可

#include<bits/stdc++.h>

using namespace std;
#define ll long long
#define mem(a,b) memset(a,b,sizeof a)
const int N = 1e5+9;
ll mx[N];
ll l[N],r[N],a[N];
ll stk[N];
int tt,n;
ll solve()
{
		mem(stk,0);
	tt=0;
	for(int i=1; i<=n; i++)
	{
		while(tt&&a[stk[tt]]<=a[i]) tt--;
		l[i]=stk[tt];
		stk[++tt]=i;
	}
	tt=0;
	stk[tt]=n+1;
	for(int i=n; i>=1; i--)
	{
		while(tt&&a[stk[tt]]<a[i]) tt--;
		r[i]=stk[tt];
		stk[++tt]=i;
		ll x=i-l[i]-1;//a[i]左边比它大的数的个数 ,这里表示连续的 
		ll y=r[i]-i-1; 
		mx[i]=y+x*(y+1);	
	}
	ll res=0;
	for(int i=1; i<=n; i++)
	{
		//cout<<a[i]<<" "<<mx[i]<<endl;
		res+=a[i]*mx[i]; 
	}
//	cout<<res<<endl;
	return res;
}
int main()
{
	int t;
	cin>>t;
	while(t--)
	{
	
		scanf("%d",&n);
		for(int i=1; i<=n; i++) scanf("%lld",a+i);
		ll ans=solve();
		//当a[i]最为最小值的时候出线的次数,等于-a[i]作为最大值出现的次数 
		for(int i=1; i<=n; i++) a[i]=a[i]*(-1);
		printf("%lld\n",ans+solve());
	}
	
	
	return 0;
 } 

总结

本文写得有点啰嗦,如果你有什么批评或者好的建议或者疑问,欢迎留言!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值