CodeForces - 1313C2 Skyscrapers (hard version)(单调栈+dp)

题目链接:点击这里

题目大意:
给出一个长度为 n n n 的序列 a i a_i ai ,现求一个序列 b i b_i bi ,使得其满足 b i ≤ a i b_i \le a_i biai b i b_i bi 先增后减,现在求 ∑ i = 1 n a i − ∑ i = 1 n b i \sum_{i=1}^na_i-\sum_{i=1}^nb_i i=1naii=1nbi 最小的序列 b b b ,若有多种方案输出任意一种即可

题目分析:
不难想到一个 O ( n 2 ) O(n^2) O(n2) 的暴力,枚举点 i i i 为最高点,然后 O ( n ) O(n) O(n) 更新序列,选取差值最小情况即可的即可
考虑优化,分别用 d p 1 i , d p 2 i dp1_i,dp2_i dp1i,dp2i 维护从前到后到 i i i 位置的元素和的最大值,从后到前到 i i i 的元素的最大值,那么答案就是 m a x ( d p 1 i + d p 2 i − a i ) max(dp1_i+dp2_i-a_i) max(dp1i+dp2iai) (减去 a i a_i ai 是因为 a i a_i ai 被重复计算贡献了)
接下来考虑状态转移,以 d p 1 dp1 dp1 为例( d p 2 dp2 dp2 同理): d p i = d p p o s + ( i − p o s ) a i dp_i=dp_{pos}+(i-pos)a_i dpi=dppos+(ipos)ai ,其中 p o s pos pos 为从 i i i 往前第一个满足 a p o s < a i a_{pos}<a_i apos<ai p o s pos pos 。这个 p o s pos pos 不难想到可以用一个单调栈来维护,这样就可以 O ( n ) O(n) O(n) 预处理出两个 d p dp dp 数组了。然后 O ( n ) O(n) O(n) 枚举最大值位置即可

具体细节见代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<stack>
#define ll long long
#define inf 0x3f3f3f3f
#define Inf 0x3f3f3f3f3f3f3f3f
#define int ll
using namespace std;
int read()
{
	int res = 0,flag = 1;
	char ch = getchar();
	while(ch<'0' || ch>'9')
	{
		if(ch == '-') flag = -1;
		ch = getchar();
	}
	while(ch>='0' && ch<='9')
	{
		res = (res<<3)+(res<<1)+(ch^48);//res*10+ch-'0';
		ch = getchar();
	}
	return res*flag;
}
const int maxn = 1e6+5;
const int mod = 1e9+7;
const double pi = acos(-1);
const double eps = 1e-8;
int n,maxx,a[maxn],dp1[maxn],dp2[maxn],ans[maxn];
bool flag;
stack<int>st;
signed main()
{
	n = read();
	for(int i = 1;i <= n;i++) a[i] = read(),maxx = max(maxx,a[i]);
	a[0] = a[n+1] = -inf;
	st.push(0);
	for(int i = 1;i <= n;i++)
	{
		while(!st.empty() && a[st.top()] >= a[i]) st.pop();
		int pos = st.top();
		dp1[i] = dp1[pos]+(i-pos)*a[i];
		st.push(i);
	}
	while(!st.empty()) st.pop();
	st.push(n+1);
	for(int i = n;i;i--)
	{
		while(!st.empty() && a[st.top()] >= a[i]) st.pop();
		int pos = st.top();
		dp2[i] = dp2[pos]+(pos-i)*a[i];
		st.push(i);
	}
	int maxx = 0,pos = 0;
	for(int i = 1;i <= n;i++)
	{
		if(maxx < dp1[i]+dp2[i]-a[i])
		{
			maxx = dp1[i]+dp2[i]-a[i];
			pos = i;
		}
	}
	ans[pos] = a[pos];
	for(int i = pos-1;i;i--) ans[i] = min(ans[i+1],a[i]);
	for(int i = pos+1;i <= n;i++) ans[i] = min(ans[i-1],a[i]);
	for(int i = 1;i <= n;i++) printf("%lld%c",ans[i],i==n ? '\n' : ' ');
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值