Codeforces Round #622 (Div. 2) C2. Skyscrapers (hard version)

C2. Skyscrapers (hard version)

题目传送门:

C2. Skyscrapers (hard version)

题目大意:

有n栋楼,每栋楼最高为hi,不允许存在一栋楼两边都有比他更高的楼,要求建成的楼的总高度最大。

思路:

1.用单调栈。

延续C1的想法,我们要找到一个最高点,然后向左向右全部都向低处扩展。这个最高点将序列分成了两个部分,从左到右是非递减序列,从右到左是非递减序列。那么我们利用单调栈的思想,求出每个数向左所影响的区间和向右所影响的区间,分别从左往右和从右往左求一次单调栈即可。然后在求的过程中维护前缀楼高和后缀楼高。详细见代码。

AC Code

#include
using namespace std;
typedef long long LL;
const int N=5e5+10;
LL m[N];
LL l[N],r[N];
stack<LL>st;//单调栈
int main()
{
     
    LL n;
    scanf("%lld",&n);
    for(int i=1;i<=n;i++) scanf("%lld",&m[i]);
    for(int i=1;i<=n;i++)
    {
     
        while(!st.empty()&&m[i]<m[st.top()])
            st.pop();
        if(st.empty()) l[i]=(LL)i*m[i];
        else l[i]=(LL)(i-st.top())*m[i]+l[st.top()];
        st.push(i);
    }
    while(!st.empty()) st.pop();
    for(int i=n;i>=1;i--)
    {
     
        while(!st.empty()&&m[i]<m[st.top()])
            st.pop();
        if(st.empty())  r[i]=(n-i+1)*m[i];
        else r[i]=(st.top()-i)*m[i]+r[st.top()];
        st.push(i);
    }
    LL maxn=0,idx=0;
    for(int i=1;i<=n;i++)
    {
     
        if(l[i]+r[i]-m[i]>maxn)
        {
     
            maxn=l[i]+r[i]-m[i];
            idx=i;
        }
    }
    LL ans=m[idx];
    for(int i=idx;i>=1;i--)
    {
     
        ans=min(ans,m[i]);
        m[i]=ans;
    }
    ans=m[idx];
    for(int i=idx+1;i<=n;i++)
    {
     
        ans=min(ans,m[i]);
        m[i]=ans;
    }
    for(int i=1;i<=n;i++) printf("%lld ",m[i]);
    //system("pause");
    return 0;
}

2.线段树求区间最小值+分治思想

考虑分治,solve(l,r)代表当前处理到[l,r]这个区间。假设区间的最小值是m[mid],那么必然要把区间[l,mid-1]或者[mid+1,r]里的元素都变为m[mid],否则不符合题意。然后区间最小值用线段树查找即可。最后的时间复杂度是nlogn。

#include
using namespace std;
typedef long long LL;
const int N=5e5+10;
LL m[N],n;
struct node
{
     
    int l,r;
    pair<LL,LL>pii;
}tr[N*4];
LL ans=0,idx=0;
void pushup(int x)
{
     
    if(tr[x<<1].pii.second<=tr[x<<1|1].pii.second)
    {
     
        tr[x].pii.first=tr[x<<1].pii.first;
        tr[x].pii.second=tr[x<<1].pii.second;
    }
    else 
    {
     
        tr[x].pii.first=tr[x<<1|1].pii.first;
        tr[x].pii.second=tr[x<<1|1].pii.second;
    }
    return ;
}
void build(int k,int l,int r)
{
     
    tr[k].l=l,tr[k].r=r;
    if(l==r)
    {
     
        tr[k].pii.first=l,tr[k].pii.second=m[l];
        return ;
    }
    int mid=(l+r)/2;
    build(k<<1,l,mid);
    build(k<<1|1,mid+1,r);
    pushup(k);
}
inline pair<LL,LL> query(int k,int l,int r)   //区间查询最小值
{
     
    if(tr[k].l>=l&&tr[k].r<=r) return tr[k].pii;
    int mid=(tr[k].l+tr[k].r)/2;
    pair<LL,LL>maxn;
    maxn.first=1e9+10,maxn.second=1e9+10;
    if(l<=mid) maxn=query(k<<1,l,r);
    if(r>mid)
    {
     
        pair<LL,LL>p=query(k<<1|1,l,r);
        if(p.second<maxn.second) maxn=p;
    }
    return maxn;
}
void solve(int l,int r,LL sum)
{
     
    if(l==r)
    {
     
        sum=sum+m[l];
        if(sum>ans)
        {
     
            ans=sum;
            idx=l;
        }
        return ;
    }
    pair<LL,LL>now=query(1,l,r);
    LL mid=now.first;
    if(mid+1<=r) solve(mid+1,r,sum+now.second*(mid-l+1));
    if(mid-1>=l) solve(l,mid-1,sum+now.second*(r-mid+1));
}
int main()
{
     
    scanf("%lld",&n);
    for(int i=1;i<=n;i++) scanf("%lld",&m[i]);
    build(1,1,n);
    solve(1,n,0);
    LL k=m[idx];
    for(int i=idx;i>=1;i--)
    {
     
        k=min(k,m[i]);
        m[i]=k;
    }
    k=m[idx];
    for(int i=idx+1;i<=n;i++)
    {
     
        k=min(k,m[i]);
        m[i]=k;
    }
    for(int i=1;i<=n;i++) printf("%lld ",m[i]);
    //system("pause");
    return 0;
}

不管是时间复杂度还是代码的好写度来说都是第一种方法更加优秀。

你可能感兴趣的:(Codeforces,单调栈,线段树,思维)