您的位置:首页 > 其它

BZOJ 1010 [HNOI2008]玩具装箱toy 斜率优化DP

2015-10-10 20:58 309 查看
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1010

题目大意:

N个玩具,第i个玩具长度为Ci,将他们分为任意组,若将第i个玩具到第j个玩具分为一组,则该组的长度x=j−i+∑jk=iCk,所需要花费的费用为(x−L)2L为固定值。

1≤N≤50000;1≤L,Ci≤107

题解:

如果设sum[i]=∑ik=1Ck很容易就能写出状态转移方程

f[i]=min{f[j]+(i−j−1+sum[i]−sum[j]−L)2}

这里我们不妨设sum[i]=∑ik=1Ck+i,L=L+1,使代码更加简洁

状态转移方程变为f[i]=min{f[j]+(sum[i]−sum[j]−L)2}

朴素时间复杂度O(n2)肯定是不行的,所以我们用斜率优化DP将其优化至O(n)

将状态转移方程加以变换得

f[j]+(sum[j]+L)2=sum[i]∗2∗(sum[j]+L)+f[i]−(sum[i])2

令:

y=f[j]+(sum[j]+L)2;

k=sum[i];

x=2∗(sum[j]+L);

b=f[i]−(sum[i])2;

则状态转移方程变为y=kx+b

当做状态i的决策时,可以确定斜率k将之前的状态j所对应的点(xj,yj)代入即可求出截距b,进而求出f[i]

这相当于用一条斜率确定的直线穿过符合条件的点,使截距最小

如图,易知我们所选取的点一定是下凸壳上的点



我们用一个单调队列来维护这个下凸壳,每次用队首的元素更新状态,使每次求得的f[i]最小,并插入新的元素。

代码:

#include<stdio.h>
typedef long long ll;
int n;
ll l;
ll c[50005];
ll sum[50005];
ll f[50005];
int q[50005];
int head;
int tail;
ll y(int i)
{
return f[i]+(sum[i]+l)*(sum[i]+l);
}
ll x(int i)
{
return 2ll*(sum[i]+l);
}
double getk(int a,int b)
{
return (double)(y(b)-y(a))/(x(b)-x(a));
}
int main()
{
scanf("%d%lld",&n,&l);
l++;
for(int i=1;i<=n;i++)
{
scanf("%lld",&c[i]);
sum[i]=sum[i-1]+c[i]+1;
ll k=sum[i];
while(tail-head&&getk(q[head],q[head+1])<=k)
{
head++;
}
f[i]=y(q[head])-k*x(q[head])+sum[i]*sum[i];
while(tail-head&&getk(q[tail-1],i)<=getk(q[tail-1],q[tail]))
{
tail--;
}
q[++tail]=i;
}
printf("%lld\n",f
);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  BZOJ 斜率优化DP