Codeforces Round #622 (Div. 2)

C2. Skyscrapers (hard version)

C c1和c2的题意是一样的,只是数据范围不同,
已知一个数组m,让你构造一个数组a是的任意ai<=mi,而且任意jai 我们需要假设以每个点是最高点来计算这时的数组的和取最大的情况,c1可以直接暴力,从最高点向两边去遍历求解,c2是肯定不行的。假设ai是最高点,那么ai左边的数都是单调递增的,所以我们需要在左边找到第一个小于ai的点aj,在[j+1,i]这个区间内所有点一定是大于等于ai的(如果有点小于ai,那么我们找到的j就不是第一个,所以这个区间内一定都是大于等于ai的),因为ai是最高峰,所以整个区间都等ai才满足题意且最大,那么再想aj之前的,以为区间[1,i]是单增的,所以区间[1,j]也一定得是单增的,而且j在一定在i点之前,让j先经过刚才那样的处理[1,j]的总合就求过了直接加就可以了,所以就从1到n向右遍历求解;同样得求每个点左边的和时也和上面一样不过得从右向左遍历了。
求ai左边第一个小于ai的值就可以用单调栈。附:大佬博客
单调栈解法:

#include
using namespace std;
typedef long long ll;
const int N=500010;
ll n,a[N],l[N],r[N],dpl[N],dpr[N];/*li是i左边第一个小于ai的点的下标,ri是i右边
第一个小于ai的点的下标,dpli保存i左边单调递增前i个数的最大和(包括ai),dpli是
i右边单调递减i到n所有数的最大和(包括ai);*/
 
int main()
{
	stack<int> st;
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	for(int i=1;i<=n;i++)
	{
		while(st.size()&&a[st.top()]>=a[i]) st.pop();/*求第一个小于等于ai的值的
	话,改成a[st.top()]>a[i]就可以了,注意 栈里存的是下标不是值!!!*/
		if(!st.size()) l[i]=0;
		else l[i]=st.top();
		st.push(i); //前边是单调栈的模板
		
		dpl[i]=dpl[l[i]]+(i-l[i])*a[i];
	}
	while(st.size()) st.pop();
	for(int i=n;i;i--)
	{
		while(st.size()&&a[st.top()]>=a[i]) st.pop();//和前边一样,单调递减逆序看的话就是单增了
		if(!st.size()) r[i]=n+1;
		else r[i]=st.top();
		st.push(i);
		dpr[i]=dpr[r[i]]+(r[i]-i)*a[i];
	}
	ll res=0,pos;//res保存最大值,pos保存最大值的最高峰的下标
	for(int i=1;i<=n;i++)
	{
		ll res1=dpl[i]+dpr[i]-a[i];//dpl和dpr都加了ai减去一个就行了
		if(res1>res)
		{
			res=res1;
			pos=i;
		}
	}
	for(int i=pos-1;i;i--)
		a[i]=min(a[i+1],a[i]);
	for(int i=pos+1;i<=n;i++)
		a[i]=min(a[i-1],a[i]);
	for(int i=1;i<=n;i++)
		printf("%lld ",a[i]);
	return 0;
}


你可能感兴趣的:(CodeForces)