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;
}
posted @ 2020-03-01 12:09  Leachim  阅读(128)  评论(0编辑  收藏  举报