1010: [HNOI2008]玩具装箱toy
Description
P教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京。他使用自己的压缩器进行压缩,其可以将任意物品变成一堆,再放到一种特殊的一维容器中。P教授有编号为1...N的N件玩具,第i件玩具经过压缩后变成一维长度为Ci.为了方便整理,P教授要求在一个一维容器中的玩具编号是连续的。同时如果一个一维容器中有多个玩具,那么两件玩具之间要加入一个单位长度的填充物,形式地说如果将第i件玩具到第j个玩具放到一个容器中,那么容器的长度将为 x=j-i+Sigma(Ck) i<=K<=j 制作容器的费用与容器的长度有关,根据教授研究,如果容器长度为x,其制作费用为(X-L)^2.其中L是一个常量。P教授不关心容器的数目,他可以制作出任意长度的容器,甚至超过L。但他希望费用最小.
Input
第一行输入两个整数N,L.接下来N行输入Ci. 1<=N<=50000 , 1<=L,Ci<=10^7
Output
输出最小费用
Sample Input
5 4
3
4
2
1
4
Sample Output
1
思路:
斜率优化裸题。
题目中选择的规律以及其无后效性的特点很容易让人想到动归,可以列出方程f[i]=min{f[j]+(i-j-1+s[i]-s[j]-L)^2},其中f[i]表示前i件物品的最小费用,s[i]表示前i件物品的长度和,即c的前缀和。但同样,观察数据可发现对于N<=50000,O(n^2)的算法是过不了的,因此我们需要对dp进行优化。观察发现,方程的形式符合f[i]=f[j]+(t[i]+t[j])^2的形式,即斜率优化的基本形态,因此,我们考虑斜率优化,具体过程如下:
当k<j<i且对于f[i]选择j比选择k更优时,有:
f[j]+(i-j-1+s[i]-s[j]-L)^2<f[k]+(i-k-1+s[i]-s[k]-L)^2
令a[i]=i+s[i]-L-1,b[j]=j+s[j]则有:
f[j]+(a[i]-b[j])^2<f[k]+(a[i]-b[k])^2
拆开,整理得:
f[j]-2a[i]b[j]+b[j]^2<f[k]-2a[i]b[k]+b[k]^2
接着,将有i的项归类到不等式右边,只含有j或k的项移项到左边,得:
f[j]+b[j]^2-(f[k]+b[k]^2)<2a[i]b[j]-2a[i]b[k]
最后,在右边只保留i相关的,左边只保留j、k相关的,得:
[(f[j]+b[j]^2)-(f[k]+b[k]^2)]/2(b[j]-b[k])<a[i]
在此时,我们发现,若令y[i]=f[i]+b[i]^2,x[i]=2b[i],则原不等式可转换为:
(y[j]-y[k])/(x[j]-x[k])<a[i]
左侧与斜率表达式(y1-y2)/(x1-x2)相似,右侧的a[i]递增,符合斜率优化的条件,套用模板即可
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=50001;
int n,l;
long long c[MAXN],s[MAXN],q[MAXN],f[MAXN];
long long getUp(int j,int k)
{
return f[j]+(j+s[j])*(j+s[j])-f[k]-(k+s[k])*(k+s[k]);
}
long long getDown(int j,int k)
{
return 2*(j+s[j]-s[k]-k);
}
int main()
{
scanf("%d%d",&n,&l);
s[0]=f[0]=0;
for(int i=1;i<=n;i++)
{
scanf("%d",&c[i]);
s[i]=s[i-1]+c[i];
}
int h,t;
h=t=0;
q[t++]=0;
for(int i=1;i<=n;i++)
{
int a=i+s[i]-l-1;
while(h+1<t&&getUp(q[h+1],q[h])<=a*getDown(q[h+1],q[h]))
h++;
f[i]=f[q[h]]+(a-q[h]-s[q[h]])*(a-q[h]-s[q[h]]);
//cout<<i<<' '<<f[i]<<endl;
while(h+1<t&&getUp(i,q[t-1])*getDown(q[t-1],q[t-2])<=getUp(q[t-1],q[t-2])*getDown(i,q[t-1]))
t--;
q[t++]=i;
}
cout<<f[n]<<endl;
return 0;
}