• 「CF1313C Skyscrapers」


    题目大意

    给出一个长度为 (N) 的序列 (a) 需要构造出一个长度为 (N) 的序列 (h) 使得 (forall i in {1,2,ldots ,N} h_i leq a_i)(forall i in {2,3,ldots ,N-1}),( exists forall j in {1,2,ldots ,i-1},k in {i+1,i+2 ldots N},h_j > h_i < h_k),求出并输出 (sum_{i=1}^{N}h_i) 达到最大值时的 (h) 序列.

    再简化一下题意,就是需要找出一个序列 (h),(h_ileq a_i),且以 (h) 中的某一个元素看来,前面和后面的元素都是单调的.

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

    Part 1

    分析

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

    代码

    #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) 的范围变大了很多达到了 (500000) 为了防止题解清一色的都是单调栈,这里是一种不是很常见的做法.

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

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

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

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

    代码

    #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;
    }
    
    
  • 相关阅读:
    服务器实现跨域
    quartz定时任务cron表达式详解
    mysql分区/分片
    微信小程序 之三元运算符代替wx:if 来解决背景图片显示隐藏
    微信小程序 本地缓存保持登录状态之wx.setStorageSync()使用技巧
    微信小程序 赋值问题
    微信小程序 wx.navigateTo()传参及多个参数方法
    js 循环遍历数组
    VUE之Router命令行警告:Named Route 'Home' has a default child route. 解决办法
    VUE之命令行报错:Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead 解决办法
  • 原文地址:https://www.cnblogs.com/Sxy_Limit/p/12355525.html
Copyright © 2020-2023  润新知