玩具装箱 TOY(HNOI2008)

本文介绍了如何解决玩具装箱问题,这是一个斜率优化动态规划(DP)的问题。通过维护一个下凸包,动态更新最小费用。在输入N件玩具的长度后,目标是找到最小制作费用,输出费用结果。文章提供了思路解析和代码实现,并强调理解斜率优化的重要性。

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

题目

P教授要去看奥运,但是他舍不下他的玩具,于是他决定把所有的玩具运到北京。他使用自己的压缩器进行压
缩,其可以将任意物品变成一堆,再放到一种特殊的一维容器中。P教授有编号为1…N的N件玩具,第i件玩具经过
压缩后变成一维长度为Ci.为了方便整理,P教授要求在一个一维容器中的玩具编号是连续的。同时如果一个一维容
器中有多个玩具,那么两件玩具之间要加入一个单位长度的填充物,形式地说如果将第i件玩具到第j个玩具放到一
个容器中,那么容器的长度将为 x=j-i+Sigma(Ck) i<=K<=j 制作容器的费用与容器的长度有关,根据教授研究,
如果容器长度为x,其制作费用为(X-L)^2.其中L是一个常量。P教授不关心容器的数目,他可以制作出任意长度的容
器,甚至超过L。但他希望费用最小.
(Sigma指的是求和~)

Input
第一行输入两个整数N,L.接下来N行输入Ci.1<=N<=50000,1<=L,Ci<=10^7

Output
输出最小费用

Sample Input
5 4
3
4
2
1
4

Sample Output
1

思路:
这道题就是一道典型的斜率优化的dp问题,好好学习下~(o゚v゚)ノ
(本来打算自己写公式的,发现博客写的话好麻烦╮(╯▽╰)╭,以后学学)
在这里插入图片描述然后我们看到最后一行式子,关键在于怎么选取j,来确定f[i]的最小值,这里我们用一个队列来维护,不断的向里面加入点,当然是有筛选的,先来看看加入新的点

在这里插入图片描述假若以及加入了O,A,B三点,这个时候我们要加入C点,因为下一步就要判断C点这个位置的最小费用,也就是最小截距,可以看到A,C两点倘若相连,那么直线无论向上还是向下倾斜,在A,C两点的截距都小于B点(因为B点在上方嘛),然后我们就可以把B点删除了把C点加入,等下来就来算C点的最小费用,这里有个简单算法,也就是满足B,C结点的斜率大于A,B结点的斜率,否则一直删除结点(这里就维护了队尾)

然后我们再看到怎样取到最小费用对于每个点

在这里插入图片描述
好,我们看到这个图,当此时结点为D的时候,我们要计算这个最小截距,我们可以通过给出的函数,在队头一个一个计算,如果gettan越来越小,就一直删除队头,也就是head++,然后就可以得到此时的最小费用,因为可以通过上面的入队可以直到我们维护的是下凸包函数,斜率越来越大,所以算到这儿就可以了得到了(大概这个意思",ԾㅂԾ,")

(斜率优化,还是要自己去想想,拿起笔写一写的嘛)
代码如下:(有注释哦~(。・∀・)ノ)

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int mx=5e4+10;

int n,l;//分别为输入个数 和常数l 
ll f[mx],s[mx];//分别为结果和 前缀和
int head,tail,h[mx];//分别为头,尾,每个数的前面加入的结点

ll  gettan(int i,int j)//得到f[i]; 
{
    return f[j]+s[j]*s[j]+(s[i]-l)*(s[i]-l)-2*s[j]*(s[i]-l);
}

bool cmp(int a,int b,int c)//比较斜率(s[x]就是自变量,f[x]+s[x]*s[x]为y)
{
    return (f[b]+s[b]*s[b]-f[a]-s[a]*s[a])*(s[c]-s[b])>=(f[c]+s[c]*s[c]-f[b]-s[b]*s[b])*(s[b]-s[a]);
}

void slove()
{
    head=tail=1;//用这个来模拟队列 
    h[tail++]=f[0]=0;
    for(int i=1;i<=n;i++)
    {
    	while(head+1<tail&&gettan(i,h[head])>=gettan(i,h[head+1]))
    	head++;//删除队列前面没有用的结点了 
    	f[i]=gettan(i,h[head]);
    	while(head+1<tail&&cmp(h[tail-2],h[tail-1],i))
    	tail--;//一直删除旧结点,满足新节点插入时斜率变大 
    	h[tail++]=i;//插入新的结点 
    }
}

int main()
{
      int k;
      scanf("%d%d",&n,&l);  
      memset(s,0,sizeof(s));
      for(int i=1;i<=n;i++) 
      {
       scanf("%d",&k);
       s[i]=s[i-1]+k;
      }
      l++;//式子变化 
      for(int i=1;i<=n;i++)//这个也是 
       s[i]+=i;
      slove();
      printf("%lld\n",f[n]);
      return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值