Codeforces 1313C2 - Skyscrapers【思维】

题意:

要求在一条直线上建 n n n 栋楼,并且要满足下列要求:
1.每栋楼的层数不能超过其楼层限制 m [ i ] m[i] m[i]
2.对于第 i , j , k i,j,k i,j,k 栋,如果满足: i < j < k i<j<k i<j<k,那么其层数要满足: a j > a i < a k a_j>a_i<a_k aj>ai<ak,其中 j , k j ,k j,k 不一定和 i i i 相邻。
求出各个楼房的实际建筑层数。

思路:

  按题目要求,最终的所有的楼房的高度可以是:递增,递减,先增后减,或者在其中加一段不变的片段,但不能出现先减后增。那么最终就必然存在一个峰值,其他的楼房高度都不大于他的高度。所以,问题转化为如何找到该峰值的位置。不能以每栋楼房的限制高度为标准,找最大的,这样不能保证总的层数最小。而应该以总的楼层数为标准(废话 ),接下来讨论如何求出以各个点为峰值时对应的总楼层数。
  数组 l [ i ] l[i] l[i] 表示:以楼房 i i i 为峰值时,其左边(包括自己)所有楼房的总层数。
  数组 r [ i ] r[i] r[i] 表示:以楼房 i i i 为峰值时,其右边(包括自己)所有楼房的总层数。
  先讨论 l [ i ] l[i] l[i] 数组的求法,对于楼房 i i i 而言,如果m[i]是1~i中的最小值,那么 l [ i ] = i ∗ m [ i ] l[i]=i*m[i] l[i]=im[i];否则,若 j j j i i i 前面满足: { m [ j ] < m [ i ]    且    ( i − j ) 最 小 } \{m[j]<m[i]\; 且\; (i-j) 最小\} {m[j]<m[i](ij)}的楼房,那么 l [ i ] = l [ j ] + m [ i ] ∗ ( i − j ) l[i]=l[j]+m[i]*(i-j) l[i]=l[j]+m[i](ij) r [ i ] r[i] r[i] 的求法同理。
关键在于如何维护每个 i i i 前面小于 m [ i ] m[i] m[i] 且最靠近 i i i的位置,具体见代码。

代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e5+5;
ll m[N],l[N],r[N];
stack<int>sta;
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%lld",&m[i]);
    for(int i=1;i<=n;i++)
    {
        while(!sta.empty()&&m[sta.top()]>m[i])
            sta.pop();
        if(sta.empty())
            l[i]=1LL*i*m[i];
        else
            l[i]=l[sta.top()]+1LL*(i-sta.top())*m[i];
        sta.push(i);
    }
    while(!sta.empty())
        sta.pop();
    for(int i=n;i>=1;i--)
    {
        while(!sta.empty()&&m[sta.top()]>m[i])
            sta.pop();
        if(sta.empty())
            r[i]=1LL*(n-i+1)*m[i];
        else
            r[i]=r[sta.top()]+1LL*(sta.top()-i)*m[i];
        sta.push(i);
    }
    ll ans=0;
    int p;
    for(int i=1;i<=n;i++)
    {
        if(l[i]+r[i]-m[i]>ans)
        {
            ans=l[i]+r[i]-m[i];
            p=i;
        }
    }
    for(int i=p-1;i>=1;i--)
    {
        if(m[i]>m[i+1])
            m[i]=m[i+1];
    }
    for(int i=p+1;i<=n;i++)
    {
        if(m[i]>m[i-1])
            m[i]=m[i-1];
    }
    for(int i=1;i<=n;i++)
        printf("%lld%c",m[i],i==n?'\n':' ');
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值