题意:
一个数组,对于某个区间,这个区间的和*这个区间中的最小值=这个区间的计算值。求这个数组中的最大计算值,并任意输出其的一个左右位置。
思路:
维护一个最小单调栈。
对于某个元素:
如果被弹出了,说明它最多向右延伸到这里。
对于进栈,如果它的进栈没有造成任何元素的弹出,则说明它的位置就是它左边能延伸到的位置。如果造成元素弹出,那么,最后一个弹出的元素的左边能延伸到的位置就是本元素往左能延伸到的位置。
对于两个元素相同时:我不作弹出处理,仅仅把这个元素的位置更新(代码中表现为把这个元素往左延伸的和更新)。
在弹出元素时,能求和,而这个和正是元素往右延伸的和。往左延伸的和已经算好,所以这个元素为最小值的最大区间值就可以知道了。
代码:
#include <cstdio> #include <cstring> #include <algorithm> #include <utility> using namespace std; #define N 110000 #define PLL pair<long long ,long long> #define MP(a,b) make_pair(a,b) int a[N]; int n; PLL st[N]; int lst[N]; int top; void solve() { a[n] = -1; top = 0; long long ans = -1; int l = 0, r = 0; int i; st[top] = MP(0,a[0]); // minnum, presum lst[top] = 0; top++; for (i = 1; i <= n; i++) { long long sum = 0; bool ispop = false; while (top && a[st[top-1].first] > a[i]) { sum += st[top-1].second; if (ans < sum * a[st[top-1].first]) { l = lst[top-1]; r = i-1; ans = sum * a[st[top-1].first]; } top--; ispop = true; } if (top && a[st[top-1].first] == a[i]) { st[top-1].second += sum + a[i]; } else { st[top] = MP(i, sum+a[i]); if (!ispop) lst[top] = i; top++; } } printf("%lld %d %d ", ans, l+1, r+1); } int main() { while (~scanf("%d", &n)) { int i; for (i = 0;i < n; i++) { scanf("%d", &a[i]); } solve(); } return 0; }
思路2(看别人的):
维护单调栈,元素被弹栈的时候,就是它能延伸到的最远点。从左到右和从右到左来两次,得到一个元素左延伸最远值和右延伸最远值,就可以得到这个元素为最小值的区间最大计算值。
代码:
#include <algorithm> #include <cstdio> #include<string.h> using namespace std; const int maxn=100010; int a[maxn],q[maxn]; long long s[maxn]; int lft[maxn],rht[maxn]; int main() { int n,i,j,k; scanf("%d",&n); for(i=1;i<=n;i++) scanf("%d",&a[i]); a[0]=a[n+1]=-2100000000; int r=0; for(i=1;i<=n+1;i++) { while(r&&a[q[r]]>a[i]) rht[q[r]]=i,r--; q[++r]=i; } r=0; for(i=n;i>=0;i--) { while(r&&a[q[r]]>a[i]) lft[q[r]]=i,r--; q[++r]=i; } for(i=1;i<=n;i++) s[i]=s[i-1]+a[i]; long long ans=-1; int l; for(i=1;i<=n;i++) { if(ans<(s[rht[i]-1]-s[lft[i]])*a[i]) ans=max(ans,(s[rht[i]-1]-s[lft[i]])*a[i]),l=lft[i]+1,r=rht[i]-1; } printf("%I64d %d %d ",ans,l,r); }