码迷,mamicode.com
首页 > 其他好文 > 详细

「CF1313C Skyscrapers」

时间:2020-02-24 09:52:42      阅读:39      评论:0      收藏:0      [点我收藏+]

标签:位置   维护   build   题解   ==   using   代码   题意   常见   

题目大意

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

再简化一下题意,就是需要找出一个序列 \(h\),\(h_i\leq 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;
}

「CF1313C Skyscrapers」

标签:位置   维护   build   题解   ==   using   代码   题意   常见   

原文地址:https://www.cnblogs.com/Sxy_Limit/p/12355525.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!