Processing math: 100%

『玩具装箱TOY 斜率优化DP』

Parsnip·2019-01-26 15:05·530 次阅读

『玩具装箱TOY 斜率优化DP』

<更新提示>

<第一次更新>


<正文>

玩具装箱TOY(HNOI2008)#

Description#

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

Input Format#

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

Output Format#

输出最小费用

Sample Input#

Copy
5 4 3 4 2 1 4

Sample Output#

Copy
1

解析#

简单分析题目大意:给定一个数列,可以将其分为若干个连续的子段,将区间[i,j]分为一段的花费为(ji+jk=iCkL)2,求最小划分总花费。
由于没有需要划分多少段的限制,可以直接简单地设置状态:f[i]代表完成了前i个数的划分的最小花费和。
区间的花费是利用前缀和可以O(1)求解的,那么状态转移方程就是fi=minfj+(i(j+1)+sumisumjL)2
暴力思路:O(n2)枚举i,j求解。
但是50000的数据O(n2)肯定不行啊,我们考虑优化这个dp。
先化简括号内花费内容:

(i(j+1)+sumisumjL)2=(ij1+sumisumjL)2=((i+sumi)+(L1jsumj))2ak=(k+sumk),bk=(ak+L+1)=(aibj)2

代回原式:

fi=min{fj+(aibj)2}

假设我们已经找到了最优的fj,那么:

fi=fj+(aibj)2fi=fj+a2i2aibj+b2jfj+b2j=2aibj+(fia2i)

(将只与i有关的项和只与j有关的项分别整理)
这个时候我们发现式子中有一项是即和i有关有和j有关的,然后就可以做一些神奇的优化了。
我们视fj+b2jybjx2aik(fi+a2i)b,那么我们就可以把它当做一个一次函数的的表达式y=kx+b。而且这个一次函数需满足:

1.过定点(bj,fj+b2j),由于这个点只和j有关,称其为Pj
2.其斜率为2ai

这时候,我们需要重新定义fi的含义:满足两个要求的一次函数的截距(b)加上a2i
由于a2i的值是确定的,我们需要求最小的fi,即求做小的一次函数的截距。
我们将满足斜率为2ai的直线以及点P1,P2,...,Pj描述在图中:

enter image description here

直线自下向上移动,只要它过任何一个点P,它就成为了符合要求的直线。
我们要求截距最小,显然,当它自下向上移动过的第一个点Pj时,截距最小。如图,此时能取得截距最小的点是P2。那么由上可知,j=2时,一次函数的截距最小,更新得到的fi也是最小的。
所以,我们的目标就转变为了找到一次函数能够第一个“碰到”的点。

enter image description here

观察上图,我们将最有可能与直线先“碰到”的点(最外围的点)两两相连,发现:它们近似的构成了一个下凸壳的形状,两两相连的线段所在的直线的斜率依次递增
此时我们发现了一个很好的性质,我们的目标直线第一个过的点最优点Pj满足:Pj,Pj+1所在直线是斜率大于目标直线斜率的第一条直线。(P2,P3构成的直线的斜率大于图中直线的斜率,但P1,P2构成的直线的斜率小于图中直线的斜率,所以P2为最优点)

那么这就成为了我们的突破口了:由于构成下凸壳直线满足斜率单调递增,我们用单调队列维护这些P点。由于随着i的增加,ai也是单调递增的,这就应和了单调队列的操作。
对于每一个i,我们让斜率小于2ai的单调队列中的直线所对应的点出队(它小于当前的2ai,由于ai单调递增,它也一定小于以后的2ai,所以这些点将会是一直无用的,可以直接出队),直到找到第一条斜率大于2ai的直线,此时,队头的点的编号j即为最优决策。我们直接利用该决策转移。
完成转移以后,新的点Pi也需要加入队列里,此时,我们还要维护一个操作:

enter image description here

Pi(图中的P7)加入后,我们发现,原来的P4,P3构成的直线的斜率大于P3和新的P4构成的斜率,这样,P4就到了内部,新的下凸壳由P1,P2,P3,P7构成,我们需要让P4出队。
即当slope(q[tail],q[tail1])>slope(q[tail1],i)(slope代表斜率函数)时,队尾出队。

事实上,我们在不断维护一个下凸壳的形状,由于单调队列一共会进出至多N个点,所以,这样实现转移的时间复杂度是O(n)的,我们将此种优化方法称为斜率优化。

Code:

Copy
#include<bits/stdc++.h> using namespace std; const int N=50000+80; long long f[N],q[N],sum[N],toy[N],n,L,head,tail; inline long long a(long long k){return sum[k]+k;} inline long long b(long long k){return a(k)+L+1;} inline long long x(long long k){return b(k);} inline long long y(long long k){return f[k]+b(k)*b(k);} inline double slope(long long p,long long q){return ((y(p)-y(q))*1.0)/((x(p)-x(q))*1.0);} int main(void) { freopen("toy.in","r",stdin); freopen("toy.out","w",stdout); scanf("%lld%lld",&n,&L); for(int i=1;i<=n;i++) { scanf("%lld",&toy[i]); sum[i]=sum[i-1]+toy[i]; } for(int i=1;i<=n;i++) { while(head<tail&&slope(q[head],q[head+1])<2*a(i))head++; f[i]=f[q[head]]+(a(i)-b(q[head]))*(a(i)-b(q[head])); while(head<tail&&slope(q[tail],q[tail-1])>slope(q[tail-1],i))tail--; q[++tail]=i; } printf("%lld\n",f[n]); }

<后记>

posted @   Parsnip  阅读(530)  评论(0编辑  收藏  举报
编辑推荐:
· 对象命名为何需要避免'-er'和'-or'后缀
· SQL Server如何跟踪自动统计信息更新?
· AI与.NET技术实操系列:使用Catalyst进行自然语言处理
· 分享一个我遇到过的“量子力学”级别的BUG。
· Linux系列:如何调试 malloc 的底层源码
阅读排行:
· JDK 24 发布,新特性解读!
· C# 中比较实用的关键字,基础高频面试题!
· .NET 10 Preview 2 增强了 Blazor 和.NET MAUI
· Ollama系列05:Ollama API 使用指南
· 为什么AI教师难以实现
点击右上角即可分享
微信分享提示
目录