【BZOJ】1010 玩具装箱

分析

预处理前缀和

sumi=j=1iai
,为区间求和作准备。

这显然是dp。
fi 表示前 i 件玩具花的最小费用。
则有:
①边界条件:f0=0
②动态转移方程: fi=min(fj+(ij1+sumisumjL)2)
③答案: fn

直接求解,时间复杂度为 O(n2) ,显然会TLE。
考虑斜率优化

原来的方程太复杂了,我们先化简一下。
通过换元法化简动态转移方程,将参量、变量、常量分离
gi=i+sumigj=j+sumjC=1+L
fi=min(fj+(gigjC)2)

求解斜率方程
为了加快求解,我们设法尽可能多的排除一些状态,这需要决策点之间的比较,我们要找出决策点比较的式子。

设当前要求 fi ,存在决策点 k,j k>j ,满足决策 k 优于决策j
fk+(gigkC)2fj+(gigjC)2
fk+(gkC)2+2gi(gkC)fj+(gjC)2+2gi(gjC)
fk+(gk+C)2fj(gj+C)22gi(gkgj)
k>jsumk>sumj
k+sumk>j+sumj gk>gj
slope(k,j)=fk+(gk+C)2fj(gj+C)2gkgj2gi

不难发现, k j可以看做两个定点,不因 i 的改变而改变:
(fk+(gk+C)2gk) (fj+(gj+C)2gj)
近一步, iN+ ,决策点 i 可以表示为平面上的点(fi+(gi+C)2gi)

对于 k,jN+ k>j
①当 slope(k,j)2gi 时,决策点 k 优于决策点j
②当 slope(k,j)>2gi 时,决策点 k 劣于决策点j

可以将问题转化为平面上的点的问题

现在问题转化为:
给定平面上 n 个在横纵坐标都单调递增的点,给定一个斜率k,支持以下三种操作:
①插入点操作:
在第 n 个点(xn,yn)的末尾增加一个点 n+1 ,也满足 xn+1>xnyn+1>yn
②询问点操作:
在点集 X={(x1,y1),(x2,y2),...,(xn,yn)} 中寻找一个点 i ,使得经过i斜率为 k 的直线在其他所有点之下;
③更改k操作:
k 变成k,满足 k>k

要寻找的点 i ,很明显i在点集的下凸壳。
又由于 k 是不断增大的,所以凸壳上要找的点i的横坐标也在不断增大。

图

我们只需要维护一个单调队列即可。

队首维护:
队首的点为 A B,若 slope(B,A)2gi ,则将队首往后移一位;

队尾维护:
队末三个点为 A B C
slope(A,B)>slope(A,C),则将 B 移出队。

每个点进队一次,出队一次。
所以总的时间复杂度为O(n),空间复杂度也为 O(n)

总结

总算彻底弄清斜率优化的严谨过程了。

代码

#include <cstdio>
#include <cctype>

typedef long long Lint;
const int N=65536;

int n,l;
int c[N];

Lint sum[N];

Lint g[N];
Lint C;
Lint f[N];
Lint x[N];
Lint y[N];

int q[N];
int qh,qt;

inline int Read(void)
{
    int x=0; char c=getchar();
    for (;!isdigit(c);c=getchar());
    for (;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x;
}

inline double Slope(int i,int j)
{
    return ((double)y[i]-y[j])/(x[i]-x[j]);
}

int main(void)
{
    n=Read(),l=Read();
    for (int i=1;i<=n;i++) c[i]=Read();

    for (int i=1;i<=n;i++) sum[i]=sum[i-1]+c[i];

    C=1+l;
    for (int i=1;i<=n;i++) g[i]=sum[i]+i;
    f[0]=0;
    x[0]=g[0];
    y[0]=f[0]+(g[0]+C)*(g[0]+C);
    q[qh=qt=1]=0;

    for (int i=1;i<=n;i++)
    {
        for (;qh!=qt&&Slope(q[qh+1],q[qh])<=2.0*g[i];q[qh++]=0);
        f[i]=f[q[qh]]+(g[i]-g[q[qh]]-C)*(g[i]-g[q[qh]]-C);
        x[i]=g[i];
        y[i]=f[i]+(g[i]+C)*(g[i]+C);
        for (;qh<qt&&Slope(q[qt-1],i)<Slope(q[qt-1],q[qt]);q[qt--]=0);
        q[++qt]=i;
    }

    printf("%lld\n",f[n]);

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值