题目描述
给定一个数组序列,需要选出一个区间,使得该区间是所有区间中经过如下计算的值最大的一个。
区间中的最小数 * 区间所有数的和
最后程序输出经过计算后的最大值即可,不需要输出具体的区间。如给定序列[6,2,1]可得到左右可以选定各个区间的计算值:
[6]=6*6=36
[2]=2*2=4;
[1]=1*1=1;
[6,2]=2*8=16;
[2,1]=1*3=3;
[6,2,1]=1*9=9
则程序输出36
区间所有数字都在【0,100】的范围内。
解题思路:
可以看出我们需要维护一个区间,同时我们能知道区间的最小值,和这个区间所有数字的和。
但是,有一点我们可以清楚知道,如果一个区间的最小值已经确定,那么我们就要尽量使得区间的和最大。其实本问题就可以转换到遍历数组,以当前位置为最小值,向左向右>=当前值能扩展的最大距离问题,我们知道这样的方法时间复杂度是O(n^2)。
嗯 区间的最小值和区间边界我们可以用单调栈去维护,这样时间复杂度就降到O(n)
其实大小都有了,输出区间边界也就不难了。我这里 输出了最大值和 区间的开始结束位置。如果有多个区间=max,我输出的是字典序较小的区间,代码如下:
给几个比较有用的数据
1
0
ans:
0
1 1
---------------------
9
1 1 1 1 1 1 1 2 2
ans:
11
1 9
----------------
4
1 3 2 4
ans:
18
2 4
----------------
#include <iostream> #include <stdio.h> #include <vector> using namespace std; typedef long long ll; ll a[100010]; class node { public: ll left, right,x; }; ll sum[100010]; int main() { // 维护一个递增的栈 int n; vector<node> st; while (cin>>n) { ll ans = -1; if (n == 0) { cout << 0 << endl << 0 << " " << 0 << endl;continue; } ll l = 0, r = 0; while (!st.empty()) st.pop_back(); sum[0] = 0; for (int i = 1; i <= n; ++i) { scanf("%lld",&a[i]); sum[i] = sum[i-1] + a[i]; } for (int i = 1; i <= n; ++i) { if (st.empty()) { st.push_back({i,i,a[i]}); } else { node u = st.back(); if (a[i] > u.x) { st.push_back({i,i,a[i]}); } else { int ileft=0,iright=0; while (!st.empty()) { node v = st.back(); if (v.x >= a[i]) { st.pop_back(); ileft = v.left; if(st.size()) st.back().right = v.right; ll tmp = (sum[v.right] - sum[v.left-1])*v.x; if (tmp >= ans) { ans = tmp; l = v.left; r = v.right; } } else break; } st.push_back({ileft,i,a[i]}); } } } while (!st.empty()) { node v = st.back(); st.pop_back(); if(st.size()) st.back().right = v.right; ll tmp = (sum[v.right] - sum[v.left-1])*v.x; if (tmp >= ans){ ans = tmp; l = v.left; r = v.right; } } cout << ans << endl << l << " " << r << endl; } return 0; }