[luogu P3195] [HNOI2008]玩具装箱TOY

[luogu P3195] [HNOI2008]玩具装箱TOY

题目描述

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。但他希望费用最小.

输入输出格式

输入格式:

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

输出格式:

输出最小费用

输入输出样例

输入样例#1:

5 4

3

4

2

1

4

输出样例#1:

1

 

开启斜率优化DP的学习。(可能因为状态原因,学的比较慢)

不太会讲理论,就对这道题讲一讲吧。

首先,我们可以列出一个n^2的转移方程:

f[i]=min(f[j]+(i-j-1+(s[i]-s[j])-L)^2)(j<i)。

将这个式子变化一下:

f[i]=min(f[j]+((i+s[i]-L-1)-(j+s[j]))^2)

设a[i]=i+s[i]-L-1,b[i]=i+s[i]。(当然也可以用一个数组,这里为了方便分辨)

则f[i]=min(f[j]+(a[i]-b[j])^2)=min(f[j]+a[i]^2-2a[i]b[j]+b[j]^2)。

即f[i]=?f[j]+a[i]^2-2a[i]b[j]+b[j]^2。

假装这是成立的,则设y=f[j]+b[j]^2,k=2a[i],x=b[j],b=f[i]-a[i]^2,

得到y=kx+b。

显然,我们需要让f[i]尽可能的小,就需要使得f[i]=b+a[i]^2尽可能小,也就是使b尽可能的小。

由于b=y-kx,其中k是确定的,y和x对于每一个j<i都是确定的,我们就可以将(x,y)表示在二维平面上。

现在,假如我们固定住x坐标,则y坐标要尽可能的小。

如果固定y坐标,则要尽可能增大x=b[j],根据前面的定义,b[]是单调递增的。但是由于y也是单调递增的,所以问题变得复杂了些。

那怎么办?这就涉及到了问题的本质。

对于一类转移方程:

f[i]=min{a[i]*b[j]+c[i]+d[j]}(a[i]和c[i]是常数,b[j]和d[j]与f[j]相关,j<i)

可以使用斜率优化。

稍微转换一下,考虑两个转移j和k,假如j比k更优,有a[i]*b[j]+c[i]+d[j]<a[i]*b[k]+c[i]+d[k]。

为方便考虑,设bj<bk,则得到-a[i]<(d[k]-d[j])/(b[k]-b[j]),这后面的就可以看成两点间直线的斜率。 

那这就提示了我们,我们可以在两点间的斜率上做点功夫。

对于这题,沿用上面的式子,得到2a[i]<k(u,v)时,u比v更优。k(u,v)=(yv-yu)/(xv-xu)

这样,我们相当于要维护一个斜率直升不降的单调队列,每次询问时找出满足k(head,head+1)>2a[i]的head,然后,将更新后的f[i]插入到单调队列的队尾。

这样,我们就能将时间复杂度降到O(N)了。(个人觉得有点凌乱,如果读者觉得有上面地方表达不妥或有误,望指出)

code:

 1 %:pragma GCC optimize(2)
 2 #include<bits/stdc++.h>
 3 #define sqr(x) ((x)*(x))
 4 #define LL long long
 5 using namespace std;
 6 const int N=50005;
 7 const double inf=1e18;
 8 int n,L,l,r; LL f[N],c[N],a[N],b[N];
 9 struct point {
10     LL x,y;
11     point() {}
12     point(LL _x,LL _y):x(_x),y(_y) {}
13 }st[N];
14 inline int read() {
15     int x=0; char ch=getchar();
16     while (ch<'0'||ch>'9') ch=getchar();
17     while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
18     return x;
19 }
20 double slope(point u,point v) {
21     return u.x==v.x?(u.y<v.y?inf:-inf):1.0*(v.y-u.y)/(v.x-u.x);
22 }
23 LL get(LL k) {
24     while (l<r&&slope(st[l],st[l+1])<1.0*k) l++;
25     return st[l].y-k*st[l].x;
26 }
27 void insert(point cur) {
28     while (l<r&&slope(st[r-1],st[r])>slope(st[r-1],cur)) r--;
29     st[++r]=cur;
30 }
31 int main() {
32     n=read(),L=read(),c[0]=0;
33     for (int i=1; i<=n; i++) c[i]=c[i-1]+read();
34     for (int i=1; i<=n; i++) a[i]=i+c[i]-L-1;
35     for (int i=1; i<=n; i++) b[i]=i+c[i];
36     memset(f,63,sizeof f);
37     l=1,r=0,st[++r]=point(0,0);
38     for (int i=1,j; i<=n; i++) {
39         f[i]=get(2*a[i])+sqr(a[i]);
40         insert(point(b[i],f[i]+b[i]*b[i]));
41     }
42     printf("%lld\n",f[n]);
43     return 0;
44 }
View Code

 

转载于:https://www.cnblogs.com/whc200305/p/7655329.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值