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;
}