题目链接:http://poj.org/problem?id=2796
【题目描述】
《人类情感研究》 special judge
比尔正在为人类情感研究发展一种新的数学理论。他最近的研究重点在于好的日子和坏的日子是如何在一段时期内影响人的记忆力的。
比尔提出了一个想法,他为人们生活的每一天都赋予了一个非负整数值。
比尔将这个值称为一天中的情感价值。一天的情感价值越大,人们在这一天就会过得越好。
比尔认为,人类生活的某一段日期的情感价值等于这段日期内情感价值的总和乘以这段日期内情感价值最小的那一天对应的情感价值。这一模式反映出:糟糕的一天会严重影响一段时期的情感价值。
请帮助比尔找出一段日期范围,使得这段日期范围内的情感价值最大。
【输入格式】
输入的第一行包含一个整数n,表示天数。(1 <= n <= 100 000)
接下来n行包含n个整数,用于表示连续的n天的情感价值。
【输出格式】
输出结果的第一行用于表示一段日期内的情感价值的最大值。
第二行结果包含两个数l和r,用于表示最大情感价值的左右坐标。
【样例输入】
6
3 1 6 4 5 2
【样例输出】
60
3 5
【题目分析】
涉及的知识点:单调栈。
这道题目翻译过来的意思其实就是:
给我们一个长度为n的数据,在其中找到一端连续子序列,使得这段连续子序列中所有元素之和乘以这段子序列中的最小值的乘积最大。
对于这道题,我们假设n个数的数组 a[1..n]。我们先来考虑最原始的做法:
对于数组中的第i个元素 a[i],我们假设以 a[i] 为最小元素去找能够包含 a[i] 的最大区间。
我们从a[i]开始一直往左边找 ≥a[i] 的元素 a[l],一直找到 l==1 或者 a[l-1]<a[i] 位置,那么这个l就对应目标区间最左边的坐标。
我们从a[i]开始一直往右边找 ≥a[i] 的元素 a[r],一直找到 r==n 或者 a[r+1]<a[i] 位置,那么这个r就对应目标区间最右边的坐标。
那么对于 a[i] 来说,以他为最小值所能够得到的最大结果就是 a[i] * (r-l+1)。
但是这样做的时间复杂度是 O(n^2) ,但我们的数据量有 10^5 ,很显然用这种方法会超时!
不过我们仔细研究的话会发现问题中的单调性,怎么说呢?
如果我们开一个结构体Node,Node中存放两个信息:
结构体中的第2个信息(先讲第2个信息比较好理解,匹配代码比较好理解)是第i节点的情感价值 h[i] ;
结构体中的第1个信息是以第i个节点的情感价值为最低值的条件下,日期范围的左边界。
怎么意思呢,比如一个长度为5的数组,它的三个元素分别为1,3,2,4,5。
那么:
1、对于 h[1] ,它的左边界为 1 (它本身);
2、对于 h[2] ,它的左边界为 2 (因为 h[1]==1 < h[2]==3)
3、对于 h[3] ,它的左边界为 2 (因为 h[2]==3 >= h[2]==2,但是 h[1]==1<h[2],所以 h[3] 的左边界最远只能到2)
4、对于 h[4] ,它的左边界为 4 (因为 h[3]>h[4] ,所以以 h[4] 为最小值的区间最左也只能到达它本身)
5、对于 h[5] ,它的左边界为 5 (同 h[4] 一样分析即可)
同样,对于上面所说的这组样例(长度为5的数组1,3,2,4,5),我们使用单调栈如何实现呢?下面我们来一步步分析:
首先我们假设我们这里有一个结构体node,node包含两个元素,第1个元素是 h[i] 对应的左边界的坐标,第2个元素是 h[i] 本身。然后我们定义一个存放 node 结构的数据结构——栈 stack。
# step.1
首先对于 h[1],因为 h[1] 是第一个元素,此时 stack 为空,此时我们将 (1, h[1]) 推入栈中。
此时栈中的元素分布如下:
(1, 1)
# step.2
对于 h[2],因为 h[2] 比 栈顶元素的高度1要大,所以以 h[2] 作为最小值的左边界,只能是 h[2] 的坐标本身,也就是2。
我们再将 (2,3) 推入栈中(其中第一个元素2对应 h[2] 的坐标,3即为h[2]的值)。
此时栈中的元素分布如下:
(2, 3)
(1, 1)
# step.3
对于 h[3],因为 h[3] 比 栈顶元素的高度3要小,-- 此处停顿一下,大家思考一下 ,然后我们继续--
大家有没有想到我接下来想说的是什么?
我之前已知说的只有元素的最低高度和左边界,但是一直没有说右边界,其实在这一步,其实我们就能够够求出了 h[2] 的右边界了。怎么说?
此时的栈顶元素高度是 h[2]==3,比当前的 h[3] 要打,也就是说,以当前栈顶元素(2,3)对应的3最为最小值的右边界也已经确定了,就是3-1==2。
所以这个时候,我们就能够得到当前栈顶元素的左边界是2,右边界也是2,最小值是3,所以以h[2]为最小值能够得到的最大区间范围是 [2,2],能够得到的最大值是 3*(2-2+1)==3。
并且,这个时候,栈顶元素对我们已经没有用了,我们可以将它pop出栈。
不过在pop出栈的同时,我们要跟新一下左边界tmp_l,这个tmp_l适用于记录我当前的元素(也即这里的h[3])的最终的左边界的,因为栈顶元素 h[2] 比 h[3] 大,所以 h[2] 的左边界已定是可以作为 h[3] 的左边界的。
pop了一个元素之后,我们再来看栈顶元素,因为栈顶元素的值 h[1] 比 h[3] 小,我们就将 h[3]对应的数据push进栈就可以了,不过这个时候 h[3] 对应的左边界不是 3 本身了,而是我们更新多的 tmp_l==2,所以我们推入栈的是 (2,2)。
事实上,只要当前栈不为空,并且当前栈的栈顶元素对应的高度大于等于 h[3],我们就要不停地进行出栈操作以及更新tmp_l操作。这是这里最重要的一个点!!!
按照这种操作,我们最终维护了一个单调的栈,其后入栈的元素的最低价值必定大于先入栈的元素的最低价值!!!
此时栈中的元素分布如下:
(2, 2)
(1, 1)
# step.4
对于 h[4]==4,我们进行和上面的分析同样的操作。直接将 h[4] 推入栈中。
此时栈中的元素分布如下:
(4, 4)
(2, 2)
(1, 1)
# step.5
对于 h[5]==5,我们进行和上面的分析同样的操作。直接将 h[4] 推入栈中。
此时栈中的元素分布如下:
(5, 5)
(4, 4)
(2, 2)
(1, 1)
进行完上述操作之后,我们还需要进行最后的处理。
对于栈中所有的元素 (l, h),我们可以知道,它的右边界都是n,所以我们要将栈中的每一个元素取出来,判断一下 (n-l+1)*h 是否大于我们的最终答案,如果是,则更新最终答案。
经过这样的操作,我们便可以得到最终的结果了。
据此,我们可以编写代码如下:
#include <cstdio> #include <stack> using namespace std; const int maxn = 100010; int n; long long h[maxn], sum[maxn]; // h[i]表示第i天的情感价值,sum[i]表示第1到i天的情感价值和 stack< pair<int,long long> > stk; // a.first 存放坐标; a.second 存放高度 long long ans; int ans_l, ans_r; void init() { ans = -1; } int main() { while (~scanf("%d", &n)) { init(); for (int i = 1; i <= n; i ++) { scanf("%lld", &h[i]); sum[i] = sum[i-1] + h[i]; } for (int i = 1; i <= n; i ++) { if (stk.empty() || stk.top().second < h[i]) stk.push(make_pair(i, h[i])); else { int tmp_l = i; while (!stk.empty() && stk.top().second >= h[i]) { pair<int, long long> pp = stk.top(); stk.pop(); tmp_l = pp.first; long long tmp_h = pp.second; long long tmp_ans = tmp_h * ( sum[i-1] - ( tmp_l == 1 ? 0 : sum[tmp_l-1] ) ); if (tmp_ans > ans) { ans = tmp_ans; ans_l = tmp_l; ans_r = i - 1; } } stk.push(make_pair(tmp_l, h[i])); } } // 最后还要处理一下栈中存留下来的所有元素 while (!stk.empty()) { pair<int, long long> pp = stk.top(); stk.pop(); int tmp_l = pp.first; long long tmp_h = pp.second; long long tmp_ans = tmp_h * ( sum[n] - (tmp_l == 1 ? 0 : sum[tmp_l-1]) ); if (tmp_ans > ans) { ans = tmp_ans; ans_l = tmp_l; ans_r = n; } } printf("%lld\n%d %d\n", ans, ans_l, ans_r); } return 0; }
不过要注意的是,这道题目比较卡数据,所以用cin和cout会超时,我这里使用了scanf和printf来进行输入输出。