【bzoj1010】【HNOI2008】【玩具装箱】【toy】【动态规划】【斜率优化】

题目大意:

有编号为1..N的N件玩具,第i件玩具长度是Ci。可以将任意编号连续的玩具变成一堆,再装到箱中。如果一堆中有多个玩具,那么每两件玩具之间要加入1个单位长度的填充物。
如果将第i到第j件玩具放在一堆中,那长度将为j-i+sigma(Ck)//i<=k<=j
制作箱的费用与箱长度有关。如果容器长度为x,其制作费用为(x-L)^2。可以制造出任意长度的箱,求最小费用。

解题思路:

设f[i]为装到第i件玩具的最小费用,显然可见
f[i]=min(f[j]+(sigma(ck)//j+1<=k<=i//+i-j-1-l)^2)
设s[i]=sigma(ck)//1<=k<=i
则f[i]=min(f[j]+(s[i]-s[j]+i-j-1-l)^2)
设g[i]=s[i]+i,c=l+1
则f[i]=min(f[j]+(g[i]-g[j]-c)^2)
则f[i]=min(f[j]+g[i]^2+g[j]^2+c^2-2g[i]g[j]-2g[i]c+2g[j]c)
考虑从f[j],f[k]j小于k,分别转移到f[i]的递推式,若从f[j]转移劣于从f[j]转移则一定满足
f[j]+g[i]^2+g[j]^2+c^2-2g[i]g[j]-2g[i]c+2g[j]c>f[k]+g[i]^2+g[k]^2+c^2-2g[i]g[k]-2g[i]c+2g[k]c
即f[j]+g[j]^2-2g[i]g[j]+2g[j]c>f[k]+g[k]^2-2g[i]g[k]+2g[k]c
即(f[j]+g[j]^2+2g[j]c)-(f[k]+g[k]^2+2g[k]c)>2g[i]*(g[j]-g[k])
因为j小于k,所以g[j]-g[k]<0
即((f[j]+g[j]^2+2g[j]c)-(f[k]+g[k]^2+2g[k]c))/(g[j]-g[k])<2g[i]
因为除法精度问题处理时须还原上一条式再判断
可见从f[j]转移或从f[j]转移于i没有半毛钱关系,至此我们想到了斜率优化动态规划。建立一个(f[j]+g[j]^2+2g[j]c,g[j])的点阵,用单调队列维护其单调性。
设函数h(j,k)=((f[j]+g[j]^2+2g[j]c)-(f[k]+g[k]^2+2g[k]c))/(g[j]-g[k])
当h(队头,队头的下一位)大于2g[i],则队头不是最优,将其踢掉
当h(队尾的前一位,队尾)大于h(队尾,i),则队尾比将要加入的i劣,将其踢掉
显然队头为当前最优,用其更新f[i]即可
具体严谨证明不在此给出,请参考其他文献

参考代码

#include<cstdio>
#include<string>
#define ll long long
#define fo(i,j,k) for(ll i=j;i<=k;i++)
using namespace std;
ll const maxn=50000;
ll n,l,s[maxn+10],f[maxn+10],que[maxn+10];
inline ll get() {
    char ch;
    while (!isdigit(ch=getchar()));
    ll v=ch-48;
    while (isdigit(ch=getchar())) v=v*10+ch-48;
    return v;
}
inline void scan(){
    n=get();l=get()+1;
    fo(i,1,n)
        s[i]=s[i-1]+get();
}
inline ll get(ll x){
    return f[x]+(s[x]+x)*(s[x]+x)+2*l*(s[x]+x);
}
inline void solve(){
    ll h=0,t=0;
    fo(i,1,n){
        while((h<t)&&(get(que[h])-get(que[h+1])>2*(s[i]+i)*(s[que[h]]+que[h]-s[que[h+1]]-que[h+1])))h++;
        f[i]=f[que[h]]+(s[i]+i-s[que[h]]-que[h]-l)*(s[i]+i-s[que[h]]-que[h]-l);
        while((h<t)&&((get(que[t-1])-get(que[t]))*(s[que[t]]+que[t]-s[i]-i)>(get(que[t])-get(i))*(s[que[t-1]]+que[t-1]-s[que[t]]-que[t])))t--;
        que[++t]=i;
    }
    printf("%lld",f[n]);
}
int main(){
    scan();
    solve();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值