玩具装箱toy

好久都没写过blog了。。。虽然本来也写的不怎么样。。。
不过今天看那个专题看了几乎一整天。。。最后才A掉一个题,而且现在也不好说到底有没有真正掌握。所以我觉得还是写一下加深印象比较好。

还是直接从题目出发吧。。
题目链接


C - 玩具装箱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。但他希望费用最小.

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题来说,普通的dp方程也非常容易找,但是数据范围:N是5e4,用普通dp的话需要O(n²),显然会T

这时候要采用一种名为斜率优化的玄学的东西,就算我看了一整天也还是似懂非懂。

我觉得网上关于斜率优化讲的都不是很清楚(大概是我太菜),(事实是我发现我自己也讲不清楚)

首先从题目出发,至于普通的dp方程,我就直接给出来了

设dp[i]表示前i个玩具所需要的最小消耗,sum[i]表示前i个物品的体积,则有

d p [ i ] = m i n ( d p [ i ] , d p [ j ] + ( s u m [ i ] − s u m [ j ] + i − j − 1 − L ) 2 ) dp[i] = min(dp[i], dp[j] + (sum[i] - sum[j] + i - j - 1 - L)^2) dp[i]=min(dp[i],dp[j]+(sum[i]sum[j]+ij1L)2)

显然,通过这个dp方程暴力解决的复杂度为O(n²),对于5e4的数据还是有点吃力的,所以我们要对其做一些优化,即所谓的斜率优化

优化的部分

先看一下原来的dp的核心代码:

dp[0] = cost(0, 0);//cost[i][j]表示合并从i到j的玩具所需要的花费
for(int i = 1; i < n; i++) {
	dp[i] = dp[i-1] + cost(i, i);//初始值
	for(int j = 0; j < i; j++)
		dp[i] = min(dp[i], dp[j] + cost(j+1, i));
}

我们斜率优化主要改变的是内层循环的部分,甚至优化到你看代码都看不出来那是个dp(我菜我菜)。

内层循环本来是要遍历前面可以选择的所有点(O(n)),然后选出“最优”。在斜率优化中,我们采用一个双端队列来存储待选的所有的点,而把不可能的点通过某种玄学方法把它筛出去,这样内层遍历的复杂度将会大大小于O(n),其伪代码为

for(int i = 1; i < n; i++) {
    维护队首();
    处理要插入的点();
    维护队尾();
    插入点();
}

在处理第i个点时,经过前面i-1次循环已经将0~i-1中所有的点都入队了,并且其中的无效的点已经被删除出去不用管

这样内层循环每次只需要遍历少量的点,可以大大减小复杂度。

维护队首

首先要明确的是删除队首的点是为了在插入下一个结点之前保证结果的最优,怎么来保证最优呢?就像算法的名字提到的一样,用到了斜率,而且在这题中,是随着遍历的深入,斜率越大,结果最优

接下来给出证明:

由于循环是有序的,且入队的方向是有序的,再加上输入是有序的,所以队列中的点也是有序的

对于队列中有序的点(什么点?可以先往下看),不妨设两个点的下标为j,k,且满足k>j,此时我们讨论当什么情况下k会优于j,从而将j出队

这时候就要引出我们的“斜率”

斜率是什么

为了表示更方便,我们将dp方程修改一下形式:

d p [ i ] = d p [ j ] + ( s u m [ i ] − s u m [ j ] + i − j − 1 − L ) 2 dp[i] = dp[j] + (sum[i] - sum[j] + i - j - 1 - L)^2 dp[i]=dp[j]+(sum[i]sum[j]+ij1L)2

令:

s [ i ] = s u m [ i ] + i ; s[i] = sum[i] + i; s[i]=sum[i]+i;

C = 1 + L ; C = 1 + L; C=

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值