传送门:POJ 2796
题目大意:给出一组数字,求一区间,使得区间元素和乘以区间最小值最大,结果要求给出这个最大值和区间的左右端点。
Sample Input
6
3 1 6 4 5 2
Sample Output
60
3 5
前置技能:单调栈的原理及应用。
思路:求序列中的最小值乘以这个序列的和的值最大,是典型的单调栈的应用。一般的思路是求每个数字所在的能使其值为区间最小值的最大区间,然后求出区间元素和乘以该值并更新结果的最大值。普通的做法时间复杂度为O(n^2),用单调栈可以达到O(n)。
具体实现:用一个单调递减栈,如果栈为空或入栈元素大于等于栈顶元素,则入栈,否则将破坏栈的单调性,则将栈顶元素出栈,直到栈为空或碰到第一个小于等于入栈元素的元素。然后将最后一次出栈的栈顶元素入栈,并将其向左右拓展,并更新其对应的值。
由于维护单调栈会改变原数组的值,同时为了方便求区间元素值,我们设置一个sum数组,记录前缀和。
我们将原数组的最后一个值设为最小值,以方便最后将栈内所有元素出栈。
注意:最后一次出栈的栈顶元素就是当前入栈元素可以向左拓展到的最大距离。
#include<stdio.h>
#include<iostream>
#include<stack>
using namespace std;
typedef long long LL;
int main()
{
int i,n,pos1,pos2; //pos1和pos2记录区间的开始和结束位置
//tmp为临时变量,记录区间内的和;top指向栈顶元素;ans为结果;sum为前缀和
LL tmp,top,ans,a[100010],sum[100010];
stack<int> st; //单调栈,记录元素位置
while(~scanf("%d",&n))
{
while(!st.empty()) st.pop(); //清空栈
sum[0]=0;
for(i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
sum[i]=sum[i-1]+a[i]; //计算前缀和
}
a[n+1]=-1; //将最后一个设为最小值,以最后让栈内元素全部出栈
ans=0;
for(i=1;i<=n+1;i++)
{
if(st.empty()||a[i]>=a[st.top()])
{ //如果栈为空或入栈元素大于等于栈顶元素,则入栈
st.push(i);
}
else
{
while(!st.empty()&&a[i]<a[st.top()])
{ //如果栈非空并且入栈元素小于栈顶元素,则将栈顶元素出栈
top=st.top();
st.pop();
tmp=sum[i-1]-sum[top-1]; //计算区间内元素和
tmp*=a[top]; //计算结果
if(tmp>=ans)
{ //更新最大值并记录位置
ans=tmp;
pos1=top;
pos2=i;
}
}
st.push(top); //将最后一次出栈的栈顶元素入栈
a[top]=a[i]; //将其向左向右延伸并更新对应的值
}
}
printf("%lld\n",ans);
printf("%d %d\n",pos1,pos2-1);
}
return 0;
}