小学二年级都能看懂的 动态规划-斜率优化学习笔记

2022.10.18 update:修改了题面错误,并增加了代码


本文主要参考oi-wiki 斜率优化

引入

n 个玩具,第 i 个玩具价值为 ci。要求将这 n 个玩具排成一排,分成若干段。对于一段 [l,r],它的代价为 (rl+i=lrciL)2 。其中 L 是一个常量,求分段的最小代价。
1n5×105

很显然,这道题可以用简单的dp实现

fi 为分到第 i 个点时最小代价,显然转移方程为:
fi=minj<i{fj1+(ij+k=jickL)2}

即使使用前缀和来维护 k=jick 时间复杂度仍然为 Θ(n2),会挂,考虑优化

斜率优化

prei 表示前 i 个数的和,我们先把转移方程写成一个更加好看的形式:

fi=minji{fj1+(ij+preiprej1L)2}

接下来就是斜率优化的具体步骤了:


1.转换问题

第一步:简化。把最外圈的 min 去掉,令 si=i+prei,L=L1 ,转移方程变为:
fi=fj1+[(prei+i)(prej1+j1)+1L]2=fj1+(sisj1L)2

第二步:展开,把所有不只含 j 的挪到一边,转移方程变为:
fisi2L2+2sisj1+2siL=fj1+sj12+2sj1L

第三步:(应该是最难理解的一步)
在每次处理 fi 时,令

k=2si

b=fisi2L2+2siL

xj=sj1

yj=fj1+sj122sj1L

原方程就能表示成:

kxj+b=yj

这非常像(就是)一个一次函数表达式

注意到:k=2sisi2L2+2siL 对于每一个 i 都是定值,而 f+sj122sj1Lsj1 对于每一个 j 也是固定的(废话)

由于 k 已经确定, b=fisi2L2+2siL ,即 b 越小,fi 越小

所以原题就可以转变成:找到一组 (xj,yj) 使得直线的截距 b 最小

2.处理

但是做完了这个之后,感觉问题不仅没有变简单,反而更复杂了更难求了……

别急,毕竟是优化,肯定是要比暴力要难处理一点的

考虑如何让直线截距最小。显然,将直线一直往上移,碰到的第一个点就能让直线截距最小

观察下面一张图:

不难发现,直线可能碰到的第一个点,在所有 (xj,yj) 构成的下凸包上,也就是说我们只需要维护下凸包即可

很显然,xj=sj1=j1+prej1 单调递增,下凸包的斜率也值单调递增,也就是说,我们只需要维护一个类似单调栈的东西,保证对于栈内的任意两条直线满足 i<j,ki<kj 即可。

更具体地讲,我们每加入一个点 i 就一直不断进行出栈,知道找到栈顶 top 使得 topi 形成的直线的斜率严格大于 toptop1 形成直线的斜率,再将 i 入栈即可

维护好了下凸包,怎么统计答案呢?
假设直线第一个碰到的点为 (xj,yj),那么在凸包上离 j 越远的点,直线肯定会越晚碰到,答案也就会更大(可以自己画图用直线切一下试试)。而 (xj,yj) 的答案又一定会小于于其他所有点。所以我们可以从栈底开始一路往栈顶找,直到该点的答案比下一个点的答案大,这个点即为直线第一个碰到的点。而底下的点与这个点相比,只可能更劣,不可能更优,所以这些点都可以直接丢掉了

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e4+5e4;
int f[N];
int c[N],l;
int pre[N],s[N];
double stk[N];
int x[N],y[N];
int top,bot=1;//bot如果初始化为0的话,i=1时有概率出锅
int n;
double cack(int i,int j){
return (y[i]-y[j])/double(x[i]-x[j]);
}
int cacans(int i,int j){
return f[j-1]+(pre[i]-pre[j-1]+i-j-l)*(pre[i]-pre[j-1]+i-j-l);
}
signed main(){
cin>>n>>l;
for(int i=1;i<=n;i++) scanf("%lld",c+i);
for(int i=1;i<=n;i++) pre[i]=pre[i-1]+c[i],s[i]=pre[i]+i;
for(int i=1;i<=n;i++){
x[i]=s[i-1],y[i]=f[i-1]+s[i-1]*s[i-1]+2*s[i-1]*(l-1);
while(bot<top&&cack(stk[top],stk[top-1])>cack(i,stk[top-1])) top--;
//记得保证栈内最少有一个元素(即bot<top)
stk[++top]=i;
while(bot<top&&cacans(i,stk[bot])>=cacans(i,stk[bot+1])) bot++;
f[i]=cacans(i,stk[bot]);
}
cout<<f[n];
}
posted @   万航之舰  阅读(80)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示