E - Skyscrapers (hard version)

E - Skyscrapers (hard version)

题意是给出一个数组m我们要构造一种数组a满足以下的条件:
1,a[i]<=m[i]
2,a只能是先增后减或者单调,中间允许相等
在所以可行的数组里面选择一个数列和最大的输出
画图、分析后可得,如果区间最大值在哪个位置确定,整个区间取到区间和最大值的那种情况就唯一确定了,贪心策略是当前结点k到前面比他小的第一个位置i都变成当前的结点值然后将i作为新的k向前递推,向后也一样;在这里插入图片描述
这样我们可以枚举每一个点作为整个序列的最大值,计算其区间和,记录所有情况里面最大的那个输出。裸的算法(对每个)是 O ( n 2 ) O(n^2) O(n2)的,但是可以一顿预处理,变成差不多 O ( n ) O(n) O(n)
对于任意一个点k设他前面比他小的点在i位置他后面比他小的点在j位置
对于i~j(不包括i,j)内的所有点可以取到最大值为m[k]
我们可以用单调栈预处理出每个点前面和后面比他小的第一个位置。之后预处理出这个点为右最高点的左边部分的总和,这个点为左最高点的右边部分的总和。这样以这个点为最大值就是左总和加右总和了。

#include<cstdio>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<map>
#include<stack>
#include<set>
#include<vector>
#include<functional>
using namespace std;
typedef long long LL;
inline LL read()
{
    LL kk=0,f=1;
    char cc=getchar();
    while(cc<'0'||cc>'9'){if(cc=='-')f=-1;cc=getchar();}
    while(cc>='0'&&cc<='9'){kk=(kk<<1)+(kk<<3)+cc-'0';cc=getchar();}
    return kk*f;
}
const int maxn=1000111;
LL n,a[maxn],pre[maxn],nex[maxn],sta[maxn],cst=0,ou[maxn];
LL sump[maxn],sumn[maxn];
void init()
{
	n=read();
	for(int i=1;i<=n;++i)a[i]=read();
	cst=0;sta[0]=pre[1]=0;sta[++cst]=1;//单调栈预处理每个点左右最小值位置
	for(int i=2;i<=n;++i)
	{
		while(a[sta[cst]]>a[i]&&cst)cst--;
		pre[i]=sta[cst];
		sta[++cst]=i;
	}
	cst=0;sta[0]=nex[n]=n+1;sta[++cst]=n;
	for(int i=n-1;i>=1;--i)
	{
		while(a[sta[cst]]>a[i]&&cst)cst--;
		nex[i]=sta[cst];
		sta[++cst]=i;
	}
	for(int i=1;i<=n;++i)sump[i]=sump[pre[i]]+(i-pre[i])*a[i];//预处理左边和
	for(int i=n;i>=1;--i)sumn[i]=sumn[nex[i]]+(nex[i]-i)*a[i];//预处理右边和
}
void gao(int x)//以x点为最大值,输出这个序列
{
	int ip,in;
	ip=in=x;
	while(ip>0)
	{
		for(int i=pre[ip]+1;i<=ip;++i)ou[i]=a[ip];
		ip=pre[ip];
	}
	while(in<=n)
	{
		for(int i=in;i<=nex[in]-1;++i)ou[i]=a[in];
		in=nex[in];
	}
	printf("%lld",ou[1]);
	for(int i=2;i<=n;++i)printf(" %lld",ou[i]);
	printf("\n");
}
int main()
{
	init();
	LL ma=1;
	for(int i=2;i<=n;++i)
	{
		LL now=sump[i]+sumn[i]-a[i];//预处理的时候实际上将当前点同时包括在左区间和右区间里面了,所以减掉一个
		LL da=sump[ma]+sumn[ma]-a[ma];
		if(da<now)ma=i;
	}
	gao(ma);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值