Upd 2025/1/26 增加模板题代码
注:文中全部图片均为手绘,不喜勿喷
总论
斜率优化是dp优化中极其常用的一种手法
第一点,先要懂什么是凸包与单调队列
先来看看凸多边形是什么样
接着再是斜率及斜率优化
一、凸包及凸多边形
凸多边形
就像这样

这就不是

即所有角都 ≤ 180 \leq180 ≤180°
凸包
一条折线,满足斜率(定义看下面)单调递增或递减,例如

二、 单调队列
请看下图

三、斜率
进入正题
什么是斜率?
严谨一点讲:
斜率 k = Δ y Δ x k=\frac{\Delta y}{\Delta x} k=ΔxΔy
其中, Δ x \Delta x Δx 与 Δ y \Delta y Δy 表示横坐标差与纵坐标差
在 Δ x \Delta x Δx 为 0 的时候,即该直线与 y y y 轴平行,该直线斜率不存在或认为趋向于 ∞
一次函数中, f ( x ) = a x + b f(x)=ax+b f(x)=ax+b,a就是其函数影像的斜率
一篇博客少不了一道题
例题——斜率
输入格式
第一行两个整数 n n n 和 m m m 。
接下来 n n n 行,每行两个整数,表示一个点的坐标。
接下来 m m m 行,每行一个整数,表示一条直线的斜率。
输出格式
对于每个斜率
k
k
k ,输出
k
x
+
y
kx+y
kx+y 的最大值, 其中
(
x
,
y
)
(x,y)
(x,y) 是给出的
n
n
n 个点中的一个。
样例
输入
5 4
-1 1
1 2
3 4
4 3
5 6
-2
3
-5
10
输出
3
21
6
56
数据范围与提示
n , m n,m n,m 的范围 [ 1 , 1 0 5 ] [1,10^5] [1,105]; x , y , k x,y,k x,y,k 的范围 [ − 1 0 9 , 1 0 9 ] [-10^9,10^9] [−109,109] 。
思路
一看这题的数据范围,暴力绝对挂,必须考虑优化
首先,暴力的思想是:
a
n
s
=
max
{
x
i
k
+
y
i
}
(
1
≤
i
≤
n
)
ans=\max\{x_ik+y_i\}(1\leq i\leq n)
ans=max{xik+yi}(1≤i≤n)
我们考虑舍去多余的判断
不妨先将节点按x轴排序
当
(
x
j
,
y
j
)
(x_j,y_j)
(xj,yj) 比
(
x
i
,
y
i
)
(
i
<
j
)
(x_i,y_i)~(i<j)
(xi,yi) (i<j) 更优时候,有
k
x
i
+
y
i
<
k
x
j
+
y
j
kx_i+y_i< kx_j+y_j
kxi+yi<kxj+yj
k
(
x
j
−
x
i
)
>
y
i
−
y
j
k(x_j-x_i)> y_i-y_j
k(xj−xi)>yi−yj
−
k
<
y
i
−
y
j
x
i
−
x
j
=
k
j
↔
i
-k<\frac{y_i-y_j}{x_i-x_j}=k_{j\leftrightarrow i}
−k<xi−xjyi−yj=kj↔i
k x ↔ y k_{x\leftrightarrow y} kx↔y 表示连接 x x x 和 y y y 的线的斜率
显然,一个点只有比两侧的点更优,才可能是最优决策点
则对于每次询问的 k k k ,若点 i i i 是决策点,一定存在 k i ↔ i − 1 ≥ − k > k i + 1 ↔ i k_{i\leftrightarrow i-1}\geq -k>k_{i+1\leftrightarrow i} ki↔i−1≥−k>ki+1↔i
很明显,后一个的斜率 < < <前一个的斜率,是一个上凸包(如图)

这样,我们的目的变为维护一个上凸包,即斜率递减
可以利用类似单调栈的方法:遇到一个点 i i i先将其与队末连线,如果产生了一个下凸包,则删掉队末……直至满足条件。然后加入 i i i点(是类似单调栈哦!与单调栈还是不同的)
均摊复杂度 O ( 1 ) O(1) O(1)(证明:因为每个数都最多加入一次,删除一次)
循环结束后
对于每个询问,如何求出最优解?
不难发现,对于询问的 k k k,
当 k j − 1 ↔ j > − k k_{j-1\leftrightarrow j}>-k kj−1↔j>−k,则 j j j一定是 [ 1 , j ] [1,j] [1,j]中的最优决策点
当 k j − 1 ↔ j ≤ − k k_{j-1\leftrightarrow j}\leq -k kj−1↔j≤−k,则 j j j一定是 [ j , n ] [j,n] [j,n]中的最优决策点
每次二分答案,可做到 O ( n log n ) O(n\log n) O(nlogn)
当然,可以利用单指针,将k从小到大排序,离线处理
证明:
因为
k
k
k越大,其答案斜率越小,可以往更后面处理,理论
O
(
n
log
n
)
O(n\log n)
O(nlogn)或
O
(
n
+
p
)
,
p
=
max
{
k
i
}
(
1
≤
i
≤
m
)
O(n+p),p=\max\{k_i\}(1\leq i\leq m)
O(n+p),p=max{ki}(1≤i≤m)
对于此题,至少 O ( n log n ) O(n\log n) O(nlogn),即快排时间复杂度或计排 + m a p +map +map时间复杂度
二、斜率优化dp
这是斜率的升级版
依然少不了一道题
例题——[SDOI2016] 征途
题目描述
Pine开始了从S地到T地的征途。
从S地到T地的路可以划分成n段,相邻两段路的分界点设有休息站。
Pine计划用m天到达T地。除第m天外,每一天晚上Pine都必须在休息站过夜。所以,一段路必须在同一天中走完。
Pine希望每一天走的路长度尽可能相近,所以他希望每一天走的路的长度的方差尽可能小。
帮助Pine求出最小方差是多少。
设方差是v,可以证明, v × m 2 v\times m^2 v×m2是一个整数。为了避免精度误差,输出结果时输出 v × m 2 v\times m^2 v×m2。
输入格式
第一行两个数 n、m。
第二行 n 个数,表示 n 段路的长度
输出格式
一个数,最小方差乘以 m 2 m^2 m2 后的值
样例 #1
样例输入 #1
5 2
1 2 5 8 6
样例输出 #1
36
提示
对于 30 % 30\% 30% 的数据, 1 ≤ n ≤ 10 1 \le n \le 10 1≤n≤10
对于 60 % 60\% 60% 的数据, 1 ≤ n ≤ 100 1 \le n \le 100 1≤n≤100
对于 $100% $ 的数据, 1 ≤ n ≤ 3000 1 \le n \le 3000 1≤n≤3000
保证从 S S S 到 T T T 的总路程不超过 30000 30000 30000 。
思路
- 第一步:方差这个东西好玄乎,拆开来!
- 第二步:欸好像可以滚掉点空间!
- 第三步:时间不够,斜率来凑
第一步
方差 S = 1 m ∑ i = 1 m ( x i − x ‾ ) 2 \texttt{方差}S=\frac{1}{m}\sum_{i=1}^m(x_i-\overline{x})^2 方差S=m1i=1∑m(xi−x)2
可以将
n
2
n^2
n2移至左侧
m
2
S
=
m
(
∑
i
=
1
m
x
i
2
)
−
2
(
∑
i
=
1
m
x
i
)
2
+
(
∑
i
=
1
m
x
i
)
2
m^2S=m(\sum_{i=1}^{m}{x_i}^2)-2(\sum^{m}_{i=1}{x_i})^2+(\sum_{i=1}^{m}x_i)^2
m2S=m(i=1∑mxi2)−2(i=1∑mxi)2+(i=1∑mxi)2
m
2
S
=
m
(
∑
i
=
1
m
x
i
2
)
−
(
∑
i
=
1
m
x
i
)
2
m^2S=m(\sum_{i=1}^{m}{x_i}^2)-(\sum_{i=1}^{m}x_i)^2
m2S=m(i=1∑mxi2)−(i=1∑mxi)2
这就是我们要输出的东西
可以发现,第二项其实是个常数, m m m也可以最后乘
这样,需要维护的东西成了平方和
记 s i = ∑ j = 1 i x j s_i=\sum_{j=1}^{i}x_j si=∑j=1ixj
d p dp dp式子就是 d p i , l = min { d p j , l − 1 + ( s i − s j ) 2 } ( i < j ) dp_{i,l}=\min\{dp_{j,l-1}+(s_i-s_j)^2\}(i<j) dpi,l=min{dpj,l−1+(si−sj)2}(i<j)
第二步(此步可省略)
可以发现,我们能够不按常理出牌,先枚举 l l l,这样可以滚掉 l l l这一层,但要倒序枚举
伪代码:
for i=1->m:
for j=n->1:
for k=1->j-1:
dp[j]=minimum(dp[j],dp[k]+(s[j]-s[k])^2)
第三步
重中之重到了!!!
我们还要继续拆式子(悲)
( s i − s j ) 2 = ( s j 2 − 2 s j s i ) + s i 2 (s_i-s_j)^2=(s_j^2-2s_js_i)+s_i^2 (si−sj)2=(sj2−2sjsi)+si2
带进
d
p
dp
dp式子内,得
d
p
i
=
min
{
d
p
j
+
s
j
2
−
2
s
j
s
i
+
s
i
2
}
dp_i=\min\{dp_j+s_j^2-2s_js_i+s_i^2\}
dpi=min{dpj+sj2−2sjsi+si2}
斜率优化有个套路,就是先提再构
提
即提出常数项
d
p
i
=
min
{
d
p
j
+
s
j
2
−
2
s
j
s
i
}
+
s
i
2
dp_i=\min\{dp_j+s_j^2-2s_js_i\}+s_i^2
dpi=min{dpj+sj2−2sjsi}+si2
构
构造一个只与
j
j
j有关的函数
f
(
j
)
=
d
p
j
+
s
j
2
f(j)=dp_j+s_j^2
f(j)=dpj+sj2
d
p
i
=
min
{
f
(
j
)
−
2
s
j
s
i
}
+
s
i
2
dp_i=\min\{f(j)-2s_js_i\}+s_i^2
dpi=min{f(j)−2sjsi}+si2
接着是斜率的老套路
一个
j
1
j_1
j1比
j
2
j_2
j2优且
j
2
<
j
1
j_2<j_1
j2<j1,仅当
f
(
j
1
)
−
2
s
j
1
s
i
>
f
(
j
2
)
−
2
s
j
2
s
i
f(j_1)-2s_{j_1}s_i>f(j_2)-2s_{j_2}s_i
f(j1)−2sj1si>f(j2)−2sj2si
f
(
j
1
)
−
f
(
j
2
)
>
2
s
i
(
j
1
−
j
2
)
f(j_1)-f(j_2)>2s_i(j_1-j_2)
f(j1)−f(j2)>2si(j1−j2)
f
(
j
1
)
−
f
(
j
2
)
2
(
j
1
−
j
2
)
<
s
i
\frac{f(j_1)-f(j_2)}{2(j_1-j_2)}<s_i
2(j1−j2)f(j1)−f(j2)<si
这样与第一题截然相反,是要维护一个下凸包
但二分还是一样的
理论复杂度 O ( n log n ) O(n\log n) O(nlogn)
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3000+7;
int n,L,s,m,as1;
int c[N],a[N],dp[2][N];
struct pos{
int a,b;
}p[2][N],nw;
int hd[2];
pos pk(pos a,pos b){
return {b.a-a.a,b.b-a.b};
}
bool cmp(pos a,pos b){
return a.b*b.a>=a.a*b.b;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(),cout.tie();
cin>>n>>m;
++L;
for(int i=1;i<=n;i++)
cin>>c[i],
a[i]=a[i-1]+c[i],
as1+=c[i];
as1*=as1;
p[0][0].a=-1;
p[0][++hd[0]]={0,0};
int c=1;
for(int t=1;t<=m;t++,c^=1){
hd[c]=0;
for(int i=1;i<=n;i++){
int x=a[i]*a[i];
int l=1,r=hd[c^1];
while(l<r){
int mid=l+r+1>>1;
if(pk(p[c^1][mid-1],p[c^1][mid]).b<a[i]*pk(p[c^1][mid-1],p[c^1][mid]).a)
l=mid;
else
r=mid-1;
}
dp[c][i]=x-p[c^1][l].a*a[i]+p[c^1][l].b;
nw.b=dp[c][i]+a[i]*a[i];
nw.a=2*a[i];
while(hd[c]>=2&&cmp(pk(p[c][hd[c]-1],p[c][hd[c]]),pk(p[c][hd[c]],nw)))
--hd[c];
p[c][++hd[c]]=nw;
}
}
cout<<dp[c^1][n]*m-as1<<endl;
}
与官方博文或参考文献对照,可更好理解
参考文献:https://www.luogu.com.cn/blog/aben-blog/xie-shuai-you-hua-dp
最新消息!最近发起活动,请选出其中最小的图片,有奖竞猜!(其实就是把名字写上去)
561




被折叠的 条评论
为什么被折叠?



