CF1313C Skyscrapers

C. Skyscrapers

原题传送门

Problem Restatement

第一行有一个整数 \(n\ (1 \leq n \leq 500000)\),表示发展商购买了 \(n\) 块地。

第二行,\(n\) 个整数 \(m_1, m_2, \ldots, m_n\ (1 \leq m_i \leq 10^9)\),表示每块地上建摩天大厦层数的限制。

求让每块地上摩天大厦的楼层数之和最大,而且存在最高点\(a_i\),让它左边的楼递增,右边的楼递减。

Solution

\(O(n^2)\)的做法就不赘述了。

关键在于在遍历最高点的时候,如何快速计算出其左右两边依次递减之和。

首先可以想到遍历用线段树维护区间和,然后二分找到上一个比该节点大的点,后面全部区间染色成\(m[i]\)即可。复杂度\(O(nlog^2n)\)。代码可参见Codeforces Submission

然而实际上可以在构造线段树的同时,维护一个区间最大值,然后利用线段树自己的二分结构进行二分,同样区间染色。复杂度\(o(nlogn)\)。这个代码我没写QAQ。

不过思考能否不维护一个完整的序列,发现可以用单调栈来维护\(i\)前面的单调上升的栈,然后利用类似DP的思想,利用栈顶元素已经求好的前缀和外加区间染色后的和。均摊复杂度为\(O(n)\)。代码如下。

Code

#include <bits/stdc++.h>
#define LL long long
#define MAXN 500005
using namespace std;

LL m[MAXN],ls[MAXN],rs[MAXN];
stack<int> st;

void solve(){
    int n;
    scanf("%d", &n);
    for(int i=1;i<=n;i++)
        scanf("%lld", &m[i]);
    for(int i=1;i<=n;i++){
        while(!st.empty() && m[st.top()]>m[i])
            st.pop();
        if(!st.empty()) ls[i]=ls[st.top()]+m[i]*(i-st.top());
        else ls[i]=m[i]*i;
        st.push(i);
    }
    st=stack<int>();
    for(int i=n;i>=1;i--){
        while(!st.empty() && m[i]<m[st.top()])
            st.pop();
        if(!st.empty()) rs[i]=rs[st.top()]+m[i]*(st.top()-i);
        else rs[i]=m[i]*(n-i+1);
        st.push(i);
    }
    LL mx=0,mi=0;
    for(int i=n;i>=1;i--)
        if(ls[i]+rs[i]-m[i]>mx)
            mx=ls[i]+rs[i]-m[i], mi=i;
    for(int i=mi-1;i>=1;i--)
        m[i]=min(m[i],m[i+1]);
    for(int i=mi+1;i<=n;i++)
        m[i]=min(m[i],m[i-1]);
    for(int i=1;i<=n;i++)
        printf("%lld ", m[i]);
}

int main(){
    int T=1;
    // scanf("%d", &T);
    while(T--){
        solve();
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/leachim/p/12388976.html