P2422 良好的感觉

题干描述

https://www.luogu.com.cn/problem/P2422

良好的感觉

题目描述

kkk 做了一个人体感觉分析器。每一天,人都有一个感受值 A i A_i Ai A i A_i Ai 越大,表示人感觉越舒适。在一段时间 [ i , j ] \left[i, j\right] [i,j] 内,人的舒适程度定义为 [ i , j ] \left[i, j\right] [i,j] 中最不舒服的那一天的感受值 × \times × [ i , j ] \left[i, j\right] [i,j]中每一天感受值的和。现在给出 kkk 在连续 N N N 天中的感受值,请问,在哪一段时间,kkk 感觉最舒适?

输入格式

第一行为 N N N,代表数据记录的天数。

第二行 N N N 个整数,代表每一天的感受值。

输出格式

一行,表示在最舒适的一段时间中的感受值。

样例 #1

样例输入 #1

6
3 1 6 4 5 2

样例输出 #1

60

提示

kkk 最开心的一段时间是第 3 3 3 天到第 5 5 5 天,开心值: ( 6 + 4 + 5 ) × 4 = 60 (6+4+5)\times4=60 (6+4+5)×4=60

对于 30 % 30\% 30% 的数据, 1 ≤ N ≤ 100 1\le N\le 100 1N100

对于 70 % 70\% 70% 的数据, 1 ≤ N ≤ 2000 1\le N\le 2000 1N2000

对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 100000 1\le N\le 100000 1N100000 1 ≤ 感受值 ≤ 1000000 1\le \texttt{感受值}\le 1000000 1感受值1000000

正文

这道题,是一个可以利用单调队列进行解决的题目,(虽然好像叫做单调栈),写的时候有很多细节问题需要考虑,所以写一篇题解

起初的思路是需要找到一个数两边不比他大的数的和与这个数的乘积,注意是两边,所以这里我们可以分左右两边进行计算,当然因为求和,而且数据很大,所以要用前缀和进行优化。

我们可以构建单调递增栈,对于每个进栈的元素x去让他找到比他小的数mi,而且先进栈的肯定在他前边,所以,找到这个数,就可以用mi~x来计算x左边的和,而且同时在维护单调栈的过程中,每个被踢出栈的元素T,其实x就是他的右端点,因为x比T小,所以每次踢出数,我们都可以将T到x的数加到sum【T】上,当做他右边的部分和。

在这里插入代码片#include<bits/stdc++.h>
using namespace std;
#define int long long 
#define N 100006
#define mod 1000000007
int a[N], q[N], dp[N], sum[N], ans;
signed main()
{
	int n, t = 0;
	cin >> n;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i]; if (i)sum[i] = sum[i - 1] + a[i];
	}
	a[++n] = 0;
	for (int i = 1; i <= n; i++)
	{
		while (a[i] < a[q[t]])
			dp[q[t]] += sum[i-1] - sum[q[t]], t--;
		//这里用sum[i-1]因为sumi已经比他小了,所以不能用
		//q[t]已经加过了,所以不能加,根据前缀和,就容易得到这个式子
		dp[i] = sum[i] - sum[q[t - 1]];
		//这里吧那个a[i]加进去了,左端和包括了a[i]
		q[++t] = i;
	}
	for (int i = 1; i <= n; i++)
		ans = max(ans, dp[i] * a[i]);
	cout << ans;
	return 0;
}


这就是基础思路,但是我们需要注意到

  1. 可能有一个很小的数最后都没有被踢掉,也就是右边没有被加上,所以要在最后加上一个0,保证他被踢掉,(其实也就是给他加上从他到整个数组右端的和)
  2. 就是一定注意不要加重复了,因为可能对于中间的数加两次,还有对这个边界的一个处理,可以输出dp[i]多检查一下

结语

这是一个很有趣的利用单调队列(单调栈)的题目,可以去试一试

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值