题干描述
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 1≤N≤100。
对于 70 % 70\% 70% 的数据, 1 ≤ N ≤ 2000 1\le N\le 2000 1≤N≤2000。
对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 100000 1\le N\le 100000 1≤N≤100000, 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;
}
这就是基础思路,但是我们需要注意到
- 可能有一个很小的数最后都没有被踢掉,也就是右边没有被加上,所以要在最后加上一个0,保证他被踢掉,(其实也就是给他加上从他到整个数组右端的和)
- 就是一定注意不要加重复了,因为可能对于中间的数加两次,还有对这个边界的一个处理,可以输出dp[i]多检查一下
结语
这是一个很有趣的利用单调队列(单调栈)的题目,可以去试一试
421

被折叠的 条评论
为什么被折叠?



