题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1010
题目大意:
有
N
个玩具,第
题解:
如果设
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
的决策时,可以确定斜率
这相当于用一条斜率确定的直线穿过符合条件的点,使截距最小
如图,易知我们所选取的点一定是下凸壳上的点
我们用一个单调队列来维护这个下凸壳,每次用队首的元素更新状态,使每次求得的
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[n]);
return 0;
}