斜率优化 DP 学习笔记
斜率优化 DP 通常可以写成基本形式:
单调队列优化 DP 的 DP 转移式通常也可以写成如下形式,不同之处在于,单调队列优化 DP 时,
先入一个引理:应该略懂一些一次函数就没问题了吧。
若函数
f(x)=kx+b 经过两个点(x_1,y_1),(x_2,y_2) ,则该函数的斜率为k=\dfrac{y_1-y_2}{x_1-x_2} 。
然后上一道例题。
P3195 [HNOI2008] 玩具装箱
首先朴素
看着很不好拆括号,不妨设
将
拆括号:
移项:
然后,将其看作
发现
也就是说也就是说假设我们用
借用一下题解区的图:
如图,对于当前直线(图上红线),不断将其平移直到其碰到某一个决策点
继续观察,不难发现对于任意直线最优决策点都一定在所有决策点的凸包上,也就是说我们只用维护凸包。考虑如何维护凸包:
记
slope(P_1,P_2) 表示经过点P_1,P_2 的函数的斜率。首先知道凸包上的点X_1,X_2,\dots,X_m 满足slope(X1,X2)<slope(X2,X3)<\dots<slope(X_{m-1},X_m) 。不妨用一个数组维护凸包上的点。设当前凸包上最后两个点分别为X_{m-1},X_m ,新加入一个点P_i 时,若slp(X_{m-1},X_m)>slp(X_m,P_i) ,则应该把点X_m 从凸包中删去,借用一下题解区的图:如图,此时
X_m 就是典型的“上凸点”,应当去除以维护凸包单调性。
然后继续考虑如何在凸包上找到最优决策点。
记最优决策点为
X_i ,其在凸包上面的前一个点记为X_{i-1} ,后一个点记为X_{i+1} ,画图可知一定有slope(X_{i-1},X_i)<k<slope(X_i,X_{i+1}) 。所以我们只需要找第一个使得
slope(X_i,X_{i+1})>k 的点X_i 即可。通过二分维护即可。
然后整合一下各个部分就差不多了吧。
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e4+5;
int n,S,L,R,s[N],a[N],b[N],dp[N],q[N];
int X(int x){return b[x];}
int Y(int x){return dp[x]+b[x]*b[x];}
long double slp(int x,int y){return 1.0*(Y(x)-Y(y))/(X(x)-X(y));}//斜率
int fd(int lt,int rt,long double k){//在决策点中二分
int ans=rt;
while(lt<=rt){
int mid=lt+rt>>1;
if(slp(q[mid+1],q[mid])>k)rt=mid-1,ans=mid;
else lt=mid+1;
}
return q[ans];
}
signed main(){
cin>>n>>S;
for(int i=1,x;i<=n;i++){cin>>x;s[i]=s[i-1]+x;}
for(int i=0;i<=n;i++)a[i]=s[i]+i,b[i]=s[i]+i+S+1;
L=R=1,q[R]=0;
for(int i=1;i<=n;i++){
int pos=fd(L,R,2.0*a[i]);//pos为最优决策点
dp[i]=dp[pos]+(a[i]-b[pos])*(a[i]-b[pos]);//用朴素的dp式子算答案即可(j=pos)
while(L<R&&slp(q[R],q[R-1])>=slp(i,q[R]))R--;//维护凸包
q[++R]=i;//维护凸包
}
cout<<dp[n];
return 0;
}
练习:
- P2120 [ZJOI2007] 仓库建设:需要处理计算斜率时
X(P_1)-X(P_2)=0 的情况。我还没调出来,100pts WA。 - P5785 [SDOI2012] 任务安排:朴素 DP 需要一个费用前置的 trick。斜率优化比较好写。
- P3628 [APIO2010] 特别行动队:需要注意系数
k 是<0 的,所以需要维护上凸包而不是下凸包。具体的,去除“上凸点”的操作需要改为去除“下凸点”的操作;二分查找也需要修改一些地方。