[tsinsen1315]积木(沈添笑)——动态规划+单调栈

题目大意:

搭积木是xx最喜欢的游戏之一。xx有n块高低不同的积木,她将它们排成一列。xx希望积木看起来尽可能的整齐,她将相邻两块积木高度之差的绝对值之和乘上系数c定义为积木序列的混乱值,显然,混乱值越小越好。xx可以通过调整积木的高度使其混乱值变小,她可以花费t^2的代价,往某块积木上再搭一块高为t(t为任意自然数)的积木,在同一块积木上只能搭一次。
xx想考考你,混乱值与花费之和的最小值是多少呢?

思路:

考虑高度的dp显然很不优秀,所有我们要想办法去除高度的影响。
假设最终序列中[l,r]中,只有l,r的高度没有发生变化,那么一个结论是[l+1,r-1]的高度必定都是相同的,简单地证明:
如果最后地序列中高度不全部相同,那么我们可以把[l+1,r-1]中最高的减少1单位,那么答案肯定是会更优的。
于是我们可以考虑一个 n 2 n^2 n2的dp:
d p [ i ] = min ⁡ ( d p [ j ] + ∑ k = j + 1 i − 1 ( x − h [ k ] ) 2 + ( h [ i ] + h [ j ] − x × 2 ) × c ) dp[i]=\min(dp[j]+\sum_{k=j+1}^{i-1}(x-h[k])^2+(h[i]+h[j]-x\times 2)\times c) dp[i]=min(dp[j]+k=j+1i1(xh[k])2+(h[i]+h[j]x×2)×c)
后面的式子可以二次函数来 O ( 1 ) O(1) O(1)求解最小值。
( i − j − 1 ) × x 2 − 2 × ( ∑ k = j + 1 i − 1 h [ k ] + c ) × x + ( ∑ k = j + 1 i − 1 h 2 [ k ] + ( h [ i ] + h [ j ] ) × c ) (i-j-1)\times x^2-2\times(\sum_{k=j+1}^{i-1}h[k]+c)\times x+(\sum_{k=j+1}^{i-1}h^2[k]+(h[i]+h[j])\times c) (ij1)×x22×(k=j+1i1h[k]+c)×x+(k=j+1i1h2[k]+(h[i]+h[j])×c)
于是考虑怎么优化枚举,不难发现只有当 max ⁡ k = j + 1 i − 1 h [ k ] ≤ min ⁡ ( h [ i ] , h [ j ] ) \max_{k=j+1}^{i-1}h[k]\leq \min(h[i],h[j]) maxk=j+1i1h[k]min(h[i],h[j])时,转移才有可能更优。比每一个点高的点只会转移一次,于是我们可以维护一个单调递减的栈,每一次在栈中转移比它矮的点,同时将元素一个一个弹出,直到转移到比它高的为止。

#include<bits/stdc++.h>

#define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
typedef long long ll;

using namespace std;

void File(){
	freopen("tsinsen1315.in","r",stdin);
	freopen("tsinsen1315.out","w",stdout);
}

template<typename T>void read(T &_){
	T __=0,mul=1; char ch=getchar();
	while(!isdigit(ch)){
		if(ch=='-')mul=-1;
		ch=getchar();
	}
	while(isdigit(ch))__=(__<<1)+(__<<3)+(ch^'0'),ch=getchar();
	_=__*mul;
}

const int maxn=1e6+10;
const ll inf=LLONG_MAX>>1;
int n;
ll c,h[maxn],sum[maxn],sum2[maxn],dp[maxn],ans=inf;
stack<int>stk;

void init(){
	read(n); read(c);
	REP(i,1,n)read(h[i]);
	REP(i,1,n){
		sum[i]=sum[i-1]+h[i];
		sum2[i]=sum2[i-1]+h[i]*h[i];
	}
}

void transfer(int j,int i,ll Min){
	if(i==j+1)return;
	ll a=(i-j-1),Max=min(h[j],h[i]);
	ll b=-2*(sum[i-1]-sum[j])-((j!=0)+(i!=n+1))*c;
	ll d=sum2[i-1]-sum2[j]+(h[j]*(j!=0)+h[i]*(i!=n+1))*c;
	ll x=round(-1.0*b/2/a);
	x=min(max(x,Min),Max);
	dp[i]=min(dp[i],dp[j]+a*x*x+b*x+d);
}

void work(){
	memset(dp,63,sizeof(dp));
	h[0]=h[n+1]=inf;
	dp[0]=0; stk.push(0);
	REP(i,1,n+1){
		dp[i]=dp[i-1]+(i!=1 && i!=n+1)*abs(h[i]-h[i-1])*c;
		ll Max=0;
		while(stk.size()!=1 && h[stk.top()]<=h[i]){
			transfer(stk.top(),i,Max);
			Max=h[stk.top()];
			stk.pop();
		}
		transfer(stk.top(),i,Max);
		stk.push(i);
	}
	printf("%lld\n",dp[n+1]);
}

int main(){
	File();
	init();
	work();
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值