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

本文介绍了一种使用斜率优化DP技术解决特定玩具分配问题的方法。通过设置状态转移方程并利用单调队列维护下凸壳,实现从O(n^2)优化至O(n)的时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1010

题目大意:
N 个玩具,第i个玩具长度为 Ci ,将他们分为任意组,若将第 i 个玩具到第j个玩具分为一组,则该组的长度 x=ji+jk=iCk ,所需要花费的费用为 (xL)2 L 为固定值。
1N50000;1L,Ci107

题解:
如果设 sum[i]=ik=1Ck 很容易就能写出状态转移方程
f[i]=min{f[j]+(ij1+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[n]);
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值