poj 2796 经典单调栈题目

题目描述:对于n个数,求一个区间,使得该区间所有值相加 乘以 该区间最小值 所得值最大

题解:单调栈的应用。题意可以这样说,求以 a[i]为区间最小值 ,求区间所有数的和乘以 a[i]的最大值是多少。这道题,我wa了一天零一夜,终于ac了。。(怪我菜咯。。)

这个过程只要维护个单调栈就行了,首先用一个数组sum去求出前缀和,用lef[i],right[i]存储以a[i]为最小值的区间的范围。求left[i] 和 right[i] 过程相似,简单说下如何求left[i]---》

left[i]就表示a[i]向左延伸能到达的下标,对于栈空,我们直接将 i 入栈,如果非空栈,我们比较栈顶 a[s.top],a[i],如果a[s.top]<a[i],直接入栈,否则出栈,把a[i]向左延伸到s.top

即left[i]=left[s.top],重复操作直至栈空或者a[s.top]<a[i],最后再把i入栈

举个例子:

对于 

6

3  1  6  4  5  2  这个样例求left[i],(从1-n编号)

初始化left[i]=i;

1.1入栈,left[1]=1,栈内元素为 1

2.a[2]<a[s.top==1],出栈,向左延伸,left[2]=1,栈内元素为 2

3.a[3]>a[s.top==2],入栈,栈内元素为 2 3

4.a[4]<a[s.top==3],出栈,向左延伸,left[4]=left[s.top==3]=3,栈内元素为 2 4

5.a[5]>a[s.top==3],入栈,栈内元素为 2 4 5

6.a[6]<a[s.top==5],出栈,向左延伸,left[6]=left[s.top==5]=5,此时a[6]<a[s.top==4],出栈,向左延伸,left[6]=left[s.top==4]=3,最后入栈,栈内元素为2 6

同理也可以求出right[i]

然后O(n)枚举i,更新ans=max(sum(Right[i])-sum(Left[i]-1))*a[i])就可以求出了。

下面贴上我的渣渣代码:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <string>
#include <queue>
#include <stack>
#include <map>
#include <cstring>
#define nloop(a,b,c) for(LL a=b;a>=c;a--)
#define loop(a,b,c) for(LL a=b;a<=c;a++)
#define clr(a,b) memset(a,b,sizeof a)
#define x first
#define y second
#define LL long long
//#define debug
#define maxn 1000005
using namespace std;
LL a[maxn],sum[maxn],Left[maxn],Right[maxn];
void init(LL n)
{
	loop(i,1,n)
			scanf("%lld",&a[i]);
	loop(i,1,n)
			sum[i]=sum[i-1]+a[i];
	loop(i,1,n)
			Left[i]=Right[i]=i;
}
void solve(LL n)
{
	stack<LL> s1,s2;
	LL ans=-1,l=0,r=0;
	loop(i,1,n)
	{
			if(s1.empty()) s1.push(i);
			else
			{
					while(!s1.empty()&&a[s1.top()]>=a[i])
                    Left[i]=Left[s1.top()],s1.pop();
					s1.push(i);
			}
	}
	nloop(i,n,1)
	{
			if(s2.empty()) s2.push(i),Right[i]=i;
			else
			{
					while(!s2.empty()&&a[s2.top()]>=a[i])
                    Right[i]=Right[s2.top()],s2.pop();
					s2.push(i);
			}
	}
	loop(i,1,n)
	{
	    //cout<<Left[i]<<" "<<Right[i]<<endl;
		LL s=(sum[Right[i]]-sum[Left[i]-1]);
		if(a[i]*s>ans)
		{
			ans=a[i]*s;
			l=Left[i];
			r=Right[i];
		}
	}
    printf("%lld\n%lld %lld\n",ans,l,r);
}
int main()
{
    #ifdef debug
    freopen("data.txt","r",stdin);
	#endif
	LL n;
	scanf("%I64d",&n);
	init(n);
	solve(n);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值