P3195 [HNOI2008] 玩具装箱 & 斜率优化
from DP...
原来一直阻碍我学习斜率优化的是原转移方程推不出来呀。
终于鼓起勇气来学斜率优化了。
问题描述
- 有
n 个玩具,每个玩具长度为c_i - 需要将玩具分成若干组,每组包含连续的玩具
- 对于一组玩具从
i 到j ,容器长度x 计算为:x = \sum_{k=i}^{j} c_k + (j - i) 其中
j - i 是玩具之间的空隙数量 - 容器的制作成本为
(x - L)^2 ,其中L 是给定常数 - 目标:最小化总成本
原转移方程
定义
为了计算
- 前
j-1 个玩具的最小成本为dp[j-1] - 最后一组(从
j 到i )的成本为cost(j, i)
因此:
计算 cost(j, i)
根据容器长度公式:
成本为:
引入前缀和数组
因此,转移方程变为:
斜率优化
化式子
关于原转移方程,将其变为:
定义新数组
其中:
-
t[i] = s[i] + i -
-
C = L+1
首先将平方项展开:
再合并:
将与
问题转换
我们定义两个新函数:
-
X(j) = t[j] -
Y(j) = dp[j] + (t[j] + C)^2
代入后:
现在,关键来了!
为什么要这样定义,发现方程中要求
将
所以
所以问题:
就等价于:
对于每个
开始优化
针对每个
最优点集
假设一张图上有一些点,我们想让它们在未知斜率的情况下截距最小,那么哪些点可能会是最小的呢?
虽然不知道斜率是多少,但我们知道斜率的正负(
所以不在凸包上的一定不会为最优点,而且在凸包上的也不一定为最优点,比如说 A 点和 C 点,在 C 点被算出来之前,A 点是最优点,而在 C 点被算出来以后,A 点将永远不会成为最优点。
而且针对不同斜率,最优点也是不一样的,比如斜率稍大时可能 E 点是最优点,而当斜率稍小时 C 有可能成为最优点。所以这就是为什么说要先维护一个最优点集而不是一个最优点。
那这个点集该怎么维护呢?
发现:对于三个点
数学上,这可以通过斜率来判断:
- 如果
\frac{Y_b - Y_a}{X_b - X_a} \geq \frac{Y_c - Y_b}{X_c - X_b} ,那么B 不在凸包上
翻译过来就是,如果
如何维护
当我们计算完
当我们添加新点
判断条件:
- 如果
\text{slope}(P_{m-1}, P_m) \geq \text{slope}(P_m, P_{new}) ,那么P_m 不在凸包上,应该被移除 - 反之直接添加即可
也就是当
代码也很清新
int k=2*t[i];
int l=0,r=End;
while(l<r){
int mid=(l+r)/2;
if(slope(tb[mid],tb[mid+1])<k){
l=mid+1;
}
else{
r=mid;
}
}
dp[i]=Y(tb[l])-t[i]*2*X(tb[l])+t[i]*t[i]-2*t[i]*C;;
于是玩具装箱的二分版代码就出来了 ::::info[代码]
#include<bits/stdc++.h>
using namespace std;
#define N 50005
#define int long long
int n,L,C,End;
int c[N],sum[N],dp[N],t[N],tb[N];
int X(int i){
return t[i];
}
int Y(int i){
return dp[i]+(t[i]+C)*(t[i]+C);
}
float slope(int j,int k){
return 1.0*(Y(j)-Y(k))/(1.0*(X(j)-X(k)));
}
signed main(){
cin>>n>>L;
C=L+1;
for(int i=1;i<=n;i++){
cin>>c[i];
sum[i]=sum[i-1]+c[i];
t[i]=sum[i]+i;
}
memset(dp,0x3f3f3f,sizeof(dp));
dp[0]=0;
for(int i=1;i<=n;i++){
int k=2*t[i];
int l=0,r=End;
while(l<r){
int mid=(l+r)/2;
if(slope(tb[mid],tb[mid+1])<k){
l=mid+1;
}
else{
r=mid;
}
}
dp[i]=Y(tb[l])-t[i]*2*X(tb[l])+t[i]*t[i]-2*t[i]*C;;
while(End>=1){
if(slope(tb[End-1],tb[End])>=slope(tb[End],i))
End--;
else
break ;
}
tb[++End]=i;
}
cout<<dp[n];
return 0;
}
::::
二分查找的话,复杂度还有个