【HNOI2008】玩具装箱(斜率优化dp)

设f[i]表示前i件玩具装好所花的最小花费。
设sumc[i]表示前i件玩具的c之和。假如第k+1~i件玩具放在一起,那么间隔有i-k-1,所需的c为sumc[i]-sumc[k],所以花费为(sumc[i]+i-sumc[k]-k-1-L)^2。
所以有状态转移方程:f[i]=min\left \{ f[k]+(sumc[i]+i-sumc[k]-k-L-1)^2 \right \},1<=k<i
现在来对这个方程简化:
设a[i]=sumc[i]+i,b[i]=sumc[i]+i+L+1。那么方程可化简为:f[i]=min\left \{ f[k]+(a[i]-b[k])^2 \right \},1<=k<i

展开,去掉min,移项可得:f[k]+b[k]^2=b[k]*2*a[i]+f[i]-a[i]^2

由于f[i]要取到最小值,且a[i]满足递增,所以此处只需用单调队列维护一个下凸包即可。

#include<cstdio>
#include<cctype>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int MAXN=50005;
int N,q[MAXN];
LL L,sumc[MAXN],f[MAXN],a[MAXN],b[MAXN];

char c;
void scan(int &x)
{
	for(c=getchar();c<'0'||c>'9';c=getchar());
	for(x=0;c>='0'&&c<='9';c=getchar()) x=x*10+c-'0';
}

void scan(LL &x)
{
	for(c=getchar();c<'0'||c>'9';c=getchar());
	for(x=0;c>='0'&&c<='9';c=getchar()) x=x*10+c-'0';
}

int main()
{
	int i;
	LL x;
	scan(N);scan(L);
	for(i=1;i<=N;i++)
	{
		scan(x);
		sumc[i]=sumc[i-1]+x;
		a[i]=sumc[i]+i;
		b[i]=sumc[i]+i+L+1;
	}
	b[0]=L+1;
	
	memset(f,0x3f,sizeof(f));
	f[0]=0;
	int L=1,R=1;
	for(i=1;i<=N;i++)
	{
		while(L<R&& (f[q[L+1]]+b[q[L+1]]*b[q[L+1]]-f[q[L]]-b[q[L]]*b[q[L]]) <= 2ll*a[i]*(b[q[L+1]]-b[q[L]]))
			L++;
		f[i]=f[q[L]]+(a[i]-b[q[L]])*(a[i]-b[q[L]]);
		while(L<R&&(f[i]+b[i]*b[i]-f[q[R]]-b[q[R]]*b[q[R]])*(b[q[R]]-b[q[R-1]])<=(f[q[R]]+b[q[R]]*b[q[R]]-f[q[R-1]]-b[q[R-1]]*b[q[R-1]])*(b[i]-b[q[R]]))
			R--;
		q[++R]=i;
	}
	cout<<f[N];
	return 0;
	
}

 

展开阅读全文
©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值