The Preliminary Contest for ICPC China Nanchang National Invitational I. Max answer(单调栈 + rmq)

题目:

  •  11.46%
  •  3000ms
  •  262144K

Alice has a magic array. She suggests that the value of a interval is equal to the sum of the values in the interval, multiplied by the smallest value in the interval.

Now she is planning to find the max value of the intervals in her array. Can you help her?

Input

First line contains an integer n(1 \le n \le 5 \times 10 ^5n(1≤n≤5×105).

Second line contains nn integers represent the array a (-10^5 \le a_i \le 10^5)a(−105≤ai​≤105).

Output

One line contains an integer represent the answer of the array.

样例输入复制

5
1 2 3 4 5

样例输出复制

36

题意:给你一个序列,每一个区间有一个贡献值,等于这个区间中的最小值 × 这段区间和,求贡献值的最大值。

单调栈:可以找到左边或者右边第一个大于/小于自己的数。时间复杂度为O(N);

思路:枚举每个数作为最小值,这个数作为最小值影响的最大的区间,可以用单调栈求出。

不会的请看视频:https://www.bilibili.com/video/av23189029?from=search&seid=18026655336668737255

当求出每个数作为最小值影响的区间时

当这个数a[i]>=0时,a[i] 为正数,a[i]还是最小,所以 当前最为最小值区间的贡献值为a[i] * 影响区间的区间和;

当a[i] < 0 时,在a[i]作为最小值影响区间中,包含a[i]的区间的最小值。但是要怎么求这个区间的最小值呢,

首先我们先求出所有数的前缀和,假设以a[i]为最小值影响的区间的左、右端点分别为l,r,先求出 l~i 下标的前缀和的最大值 k1,再求出i~r下标的前缀和的最小值 k2,k2 - k1就是所要找的区间的最小值。这里需要注意,k1为负数时,要减去max(k1,sum[l-1]),求最值的话用 rmq维护前缀和就行

结论1:在一段区间[1,n] 中求包含这个数的区间的最小值:先求出所有数前缀和,再求出 1~i 下标的前缀和的最大值 k1,再求出i~n下标的前缀和的最小值 k2,k2 - k1就是所要找的包含这个数区间的最小值。

结论2:在一段区间[1,n]中求包含这个数的区间和最大值,预处理所有数的前缀和,再求出1~i下标的前缀和的最小值k1 和 i~n下标的最大值k2,k2 - k1 就是所有要找的包含这个数的区间的最大值。
案例1

-11  2  3   -5   -4

-11 -9  -6  -11 -15           看-5,-15 - max(-11,-6) = -9. (-5) *(-9) = 40.

案例2:

4  -5   -2   1   -2  -3   -2   -1  4

4   -1  -3  -2  -4   -7   -9  -10 -6           看a[i] = -3时的状态 

 

代码:

#include<bits/stdc++.h>
using namespace std;
const int Max = 5e5;
#define ll long long
struct node
{
	int val,id;
	int l,r;
}stu[Max+10];
stack<node> s;
int n,Log[Max+10],c[30];
ll sum[Max+10],dpM[Max+10][21],dpN[Max+10][21];
void D_R()    // 单调栈,求右边第一个小于它的数;	 
{
	for(int i = 1;i<=n;i++)
	{
		stu[i].r = n+1;
		while(s.size()&&s.top().val>stu[i].val)
		{
			node tt = s.top();
			s.pop();
			stu[tt.id].r = i;
		}
		s.push(stu[i]);
	}
	while(s.size())
		s.pop();
}
void D_L()
{
	for(int i = n;i>=1;i--)
	{
		stu[i].l = 0;
		while(s.size()&&s.top().val>stu[i].val)
		{
			node tt = s.top();
			s.pop();
			stu[tt.id].l = i;
		}
		s.push(stu[i]);
	}
	while(s.size())
		s.pop();
}
void rqm()
{
	for(int j = 1;j<=Log[n];j++)
		for(int i = 1;i+c[j]-1<=n;i++){
			dpM[i][j] = max(dpM[i][j-1],dpM[i+c[j-1]][j-1]);
			dpN[i][j] = min(dpN[i][j-1],dpN[i+c[j-1]][j-1]); 
		}
	
}
int main()
{	
   c[0] = 1;
	for(int i = 1;i<=21;i++)
		c[i] = c[i-1] * 2;
	scanf("%d",&n);
	Log[0] = -1;
	for(int i = 1;i<=n;i++){
		scanf("%d",&stu[i].val);
		Log[i] = Log[i>>1] + 1;
		stu[i].id = i;
		sum[i] = sum[i-1] + (ll)stu[i].val; 
		dpM[i][0] = dpN[i][0] = sum[i];
	}
	rqm();
	D_R();	
	D_L();
	ll ans = -0x3f3f3f3f3f3f3f;
	for(int i = 1;i<=n;i++){
		if(stu[i].val>=0) 
			ans = max((ll)stu[i].val*(sum[stu[i].r-1]-sum[stu[i].l]),ans);
		else{
			
			int l = stu[i].l+1,r = stu[i].r - 1;
			int t1 = Log[i-l+1],t2 = Log[r-i+1];
			ll k1 = max(dpM[l][t1],dpM[i-c[t1]][t1]);
			ll k2 = min(dpN[i][t2],dpN[r-c[t2]][t2]);
			if(k1>=0) ans = max(ans,(ll)stu[i].val * (k2-k1));
			else  ans = max(ans,(ll)stu[i].val*(k2-max(sum[stu[i].l],k1)));
				// 减去 max(边界值,k1)两者的较大值。 
		}
	}
	printf("%lld\n",ans);
	return 0;	
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值