CF1313C Skyscrapers

题目大意

给出一个长度为 N N N 的序列 a a a 需要构造出一个长度为 N N N 的序列 h h h 使得 ∀ i ∈ { 1 , 2 , … , N } h i ≤ a i \forall i \in \{1,2,\ldots ,N\} h_i \leq a_i i{1,2,,N}hiai ∀ i ∈ { 2 , 3 , … , N − 1 } \forall i \in \{2,3,\ldots ,N-1\} i{2,3,,N1}, ∄ ∀ j ∈ { 1 , 2 , … , i − 1 } , k ∈ { i + 1 , i + 2 … N } , h j > h i < h k \nexists \forall j \in \{1,2,\ldots ,i-1\},k \in \{i+1,i+2 \ldots N\},h_j > h_i < h_k j{1,2,,i1},k{i+1,i+2N},hj>hi<hk,求出并输出 ∑ i = 1 N h i \sum_{i=1}^{N}h_i i=1Nhi 达到最大值时的 h h h 序列.

再简化一下题意,就是需要找出一个序列 h h h, h i ≤ a i h_i\leq a_i hiai,且以 h h h 中的某一个元素看来,前面和后面的元素都是单调的.

本题在数据范围上分为两个部分,所以就写一下两种不同的做法.

Part 1

分析

N N N 的范围不大,只有 1000 1000 1000 于是很容易想到时间复杂度应该是 O ( N 2 ) O(N^2) O(N2) 的,可以想到先枚举最高的位置的点,在向两边计算,可以很容易想到最高位置的 h i = a i h_i=a_i hi=ai 而且当最高位置确定以后,为了使得和最大,所以整一个序列也是确定的,那么就可以想出最开始的 O ( N 2 ) O(N^2) O(N2) 的做法了

代码

#include<bits/stdc++.h>
#define REP(i,first,last) for(int i=first;i<=last;++i)
#define DOW(i,first,last) for(int i=first;i>=last;--i)
using namespace std;
int N,h[114514];
int top;
long long ans[114514];
int main()
{
	scanf("%d",&N);
	REP(i,1,N)
	{
		scanf("%d",&h[i]);
	}
	int now;
	long long answer=0;
	long long sum;
	REP(i,1,N)//枚举最高的位置
	{
		now=h[i];//记录当前的位置
		sum=h[i];
		DOW(j,i-1,1)//向前扫
		{
			sum+=min(h[j],now);
			now=min(h[j],now);//这里的now因为要保证单调不下降,所以需要一直取min
		}
		now=h[i];//向后扫同理
		REP(j,i+1,N)
		{
			sum+=min(h[j],now);
			now=min(h[j],now);
		}
		if(sum>answer)//记录下最大值,以及最大值时时哪一个位置达到最大值了
		{
			answer=sum;
			top=i;
		}
	}
	//最后只需要将达到最大值时的序列输出就好了
	ans[top]=h[top];
	now=h[top];
	DOW(i,top-1,1)
	{
		ans[i]=min(h[i],now);
		now=min(h[i],now);
	}
	now=h[top];
	REP(i,top+1,N)
	{
		ans[i]=min(h[i],now);
		now=min(h[i],now);
	}
	REP(i,1,N)
	{
		printf("%d ",ans[i]);
	}
	return 0;
}

Part 2

分析

N N N 的范围变大了很多达到了 500000 500000 500000 为了防止题解清一色的都是单调栈,这里是一种不是很常见的做法.

可以发现在第一种方法中如果 j ≤ i , h j ≤ h i j \leq i,h_j \leq h_i ji,hjhi 1 1 1 j j j 的最大值肯定是被计算过的,于是就可以想到用一个数组 L L L 维护从 1 1 1 开始带第 i i i 个位置时最高处为 i i i h 1 h_1 h1 h i h_i hi 为单调不下降时 h 1 h_1 h1 h i h_i hi 和的最大值,计算这个数组需要分两种情况.

  1. 第一种情况时 a i − 1 ≤ a i a_{i-1} \leq a_i ai1ai,这样 L i = L i − 1 + a i L_i=L_{i-1}+a_i Li=Li1+ai.
  2. 第二种情况就比较特殊了,没法直接从 L i − 1 L_{i-1} Li1 计算出来,需要找到上一个小于等于 a i a_i ai 的位置 j j j,那么 L i = ( j − i ) ∗ a i + L j L_i=(j-i)*a_i+L_j Li=(ji)ai+Lj,即在这之间的大于 a i a_i ai 的位置的高度都是 a i a_i ai 这样就可以保证是单调不下降.

于是就要想办法找到上一个小于 a i a_i ai 的位置,显然不可以用 O ( N ) O(N) O(N) 的做法,那么就可以用一颗线段树维护最小值,在线段树上二分找到上一个位置.

用同样的方法计算出一个数组 R R R(即从 h i h_i hi 开始到 h N h_N hN 中单调不上升的和的最大值)与 L L L 同理,这样以第 i i i 个位置为最高值时总和就变成了 L i + R i − a i L_i+R_i-a_i Li+Riai 取出最大值以后只要像方法一一样输出就好了.

代码

#include<bits/stdc++.h>
#define REP(i,first,last) for(int i=first;i<=last;++i)
#define DOW(i,first,last) for(int i=first;i>=last;--i)
using namespace std;
const int MAXN=6e5+7;
int N,M;
int arr[MAXN];
long long L[MAXN],R[MAXN];
int ans[MAXN];
//线段树部分,不多讲
struct SegmentTree
{
	int min;
}sgt[MAXN*4];
#define LSON (now<<1)
#define RSON (now<<1|1)
#define MIDDLE ((left+right)>>1)
#define LEFT LSON,left,MIDDLE
#define RIGHT RSON,MIDDLE+1,right
void PushUp(int now)
{
	sgt[now].min=min(sgt[LSON].min,sgt[RSON].min);
}
void Build(int now=1,int left=1,int right=N)
{
	if(left==right)
	{
		sgt[now].min=arr[left];
		return;
	}
	Build(LEFT);
	Build(RIGHT);
	PushUp(now);
}
int QueryL(int l,int r,int num,int now=1,int left=1,int right=N)//查询下一个小于num的值的位置
{
	if(r<left||right<l)return -1;
	if(sgt[now].min>num)
	{
		return -1;
	}
	if(left==right)
	{
		return left;
	}
	int result=QueryL(l,r,num,LEFT);//因为是下一个,所以优先查询左边
	if(result!=-1)
	{
		return result;
	}
	return QueryL(l,r,num,RIGHT);
}
int QueryR(int l,int r,int num,int now=1,int left=1,int right=N)//查询上一个小于num的值的位置
{
	if(r<left||right<l)return -1;
	if(sgt[now].min>num)
	{
		return -1;
	}
	if(left==right)
	{
		return left;
	}
	int result=QueryR(l,r,num,RIGHT);
	if(result!=-1)
	{
		return result;
	}
	return QueryR(l,r,num,LEFT);
}
#undef LSON
#undef RSON
#undef MIDDLE
#undef LEFT
#undef RIGHT
int main()
{
	scanf("%d",&N);
	REP(i,1,N)
	{
		scanf("%d",&arr[i]);
	}
	Build();
	REP(i,1,N)
	{
		if(arr[i]>=arr[i-1])//计算L时的第一种情况
		{
			L[i]=L[i-1]+1ll*arr[i];
		}
		else
		{
			int top=QueryR(1,i-1,arr[i]);//找到上一个小于的位置
			if(top==-1)L[i]=1ll*i*arr[i];//不存在就直接计算
			else
			L[i]=1ll*(i-top)*arr[i]+L[top];//存在按第二种情况计算
		}
	}
	DOW(i,N,1)//计算R同理
	{
		if(arr[i]>=arr[i+1])
		{
			R[i]=R[i+1]+1ll*arr[i];
		}
		else
		{
			int top=QueryL(i+1,N,arr[i]);
			if(top==-1)R[i]=1ll*(N-i+1)*arr[i];
			else
			R[i]=1ll*(top-i)*arr[i]+R[top];
		}
	}
	int top=1;
	long long answer=0;
	long long sum;
	REP(i,1,N)
	{
		sum=L[i]+R[i]-arr[i];
		if(sum>answer)//找到最大位置
		{
			answer=sum;
			top=i;
		}
	}
	//同样方法输出
	ans[top]=arr[top];
	int now=arr[top];
	DOW(i,top-1,1)
	{
		ans[i]=min(arr[i],now);
		now=min(arr[i],now);
	}
	now=arr[top];
	REP(i,top+1,N)
	{
		ans[i]=min(arr[i],now);
		now=min(arr[i],now);
	}
	REP(i,1,N)
	{
		printf("%d ",ans[i]);
	}
	return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值