bzoj2650. 积木

传送门

一眼看上去不太可做,先找结论。

本题的核心思路:如果一个位置一边比它大,一边比它小,且确定了两边不动,那么提升它混乱值没有减小。

重点在于两边都不动,所以考虑 \(i,j\) 是不动的,中间都是动的,发掘一下性质。

首先 \(\min(h_i,h_j)>\max_{k=j+1}^{i-1}h_k\),因为中间如果有大于等于 \(\min(h_i,h_j)\) 的,它们不提升肯定比提升优。

既然中间的要小于两边的,那么它们提升之后肯定也不会超过两边。有了这个,可以推出中间的在提升之后一定变为同一个值。proof:因为左右两边是最大,所以中间在全部变为同一值前肯定存在一个结构:\(h_{k-1}\le h_k< h_{k+1}\),且 \(h_k\) 至少提升 \(1\),但是如果 \(h_{k-1}\) 不跟上混乱值不会减小,所以 \(h_{k-1}\) 必须跟上。重复以上步骤,最终中间会变为同一个值。

设中间最后变成的值为 \(x\),显然 \(\min(h_i,h_j)\ge x\ge \max_{k=j+1}^{i-1}h_k\)\(x\) 取什么最优就是一个二次函数求最值,可以 \(\mathcal{O}(1)\) 完成。

考虑 DP,\(f_i\) 表示考虑了前 \(i\) 个,第 \(i\) 个值不动的答案,显然可以枚举 \(j\) 转移,时间 \(\mathcal{O}(n^2)\),但是有限制条件 \(\min(h_i,h_j)>\max_{k=j+1}^{i-1}h_k\),根据某场 ZR 模拟赛,这样的碗装结构只有 \(\mathcal{O}(n)\) 个(其实就差不多是单调栈出栈次数),可以用单调栈找出,时间复杂度 \(\mathcal{O}(n)\)

对于统计答案,我们可以在开头结尾加一个 INF,且它们不参与混乱值的计算,以上结论分析一下还是对的。

#include<iostream>
#include<stdio.h>
#include<ctype.h>
#include<math.h>
#define int long long
#define N 1000005
using namespace std;
inline int read(){
	int x=0,f=0; char ch=getchar();
	while(!isdigit(ch)) f|=(ch==45),ch=getchar();
	while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return f?-x:x;
}
int n,c,h[N],s[N],ss[N],f[N],st[N],top;
inline int calc(int i,int j,int l,int r){
	int A=i-j-1;
	int B=-2*(s[i-1]-s[j])-(i==n+1?0:c)-(j==0?0:c);
	int C=(ss[i-1]-ss[j])+c*((i==n+1?0:h[i])+(j==0?0:h[j]));
	int p=round(-(double)B/double(2*A)),x;
	if(l<=p && p<=r) x=p;
	else if(p<l) x=l;
	else x=r;
	return A*x*x+B*x+C;
}
signed main(){
	n=read(),c=read();
	h[0]=h[n+1]=N;
	for(int i=1;i<=n;++i){
		h[i]=read();
		s[i]=h[i]+s[i-1];
		ss[i]=h[i]*h[i]+ss[i-1];
	}
	st[++top]=0;
	for(int i=1;i<=n+1;++i){
		if(2<=i && i<=n) f[i]=f[i-1]+c*abs(h[i]-h[i-1]);
		else f[i]=f[i-1];
		while(top && h[i]>=h[st[top]]){
			top--;
			if(top){
				int j=st[top],k=st[top+1];
				f[i]=min(f[i],f[j]+calc(i,j,h[k],min(h[i],h[j])));
			}
		}
		st[++top]=i;
	}
	cout<<f[n+1];
	return 0;
}

posted @ 2022-06-24 08:51  CHiSwsz  阅读(33)  评论(0编辑  收藏  举报