斜率优化(主观理解)
是一种通过数学关系处理dp的优化。
抽象地说:
设当前状态为 \(i\) ,转移源状态为 \(j\) ,若可将转移式化为 \(f(i)g(j)+h(i)+l(j)=ans_i\) 的形式(函数值仅与主元有关,常数随便放进某个函数即可),那么,对应的:
\[\begin{matrix}
f(i)&g(j)&+&h(i)-ans_i&+&l(j)\\
-k&x&+&-b&+&y
\end{matrix}=0
\]
从而,所有的 \(j\) 都被分离进了 点 \((x,y)\) 。而 \(i\) 存在于 \(k,b\) 中。
那么,为了使 \(ans_i\) 取得最值,需要使此时的截距 \(b\) 取得最值。在 \(i\) 固定时,\(ans_i\) 仅受 \(j\) 影响,由几何关系会发现,使 \(b\) 取得最值的点 \((x,y)\) 总是位于最外围,也就是一个凸包。
那么,仅需维护这个凸包,并通过斜率 \(k\) 查询最值。
实现上,
- 如维护下边缘的凸包,斜率随 \(x\) 单调递增,在加入节点,若有原在凸包上的节点被覆盖入凸包内,应将其删去。
- 相对位置的判断是通过比较斜率实现的。
算法流程:
- 初始化节点,如 \((0,0)\) 。
- 由当前 \(i\) 计算得出 \(k,b\) 。
- 用 \(k\) 在凸包上查询得到点 \(p\) ,使得 \(\begin{cases}k_{p,p-1}<k<k_{p+1,p}&下凸包\\k_{p,p-1}>k>k_{p+1,p}&上凸包\end{cases}\)。
- 由 \(k,p,b\) 计算出 \(ans_i\) ,再由 \(ans_i\) 计算出 \(i\) 所对应的点。
- 将 \(i\) 加入凸包,若是下凸包,\(\forall p<t<i \land k_{i,p}<k_{t,p}\) 或 \(\forall i<t<p \land k_{i,p}>k_{t,p}\),将 \(t\) 移出凸包。上凸包则斜率比较取反。
如果 \(f(i)\) 等函数有单调性,会有极好的优化。如 \(f(i)\) 单调可线性查询,\(g(j)\) 单调可线性维护凸包。
以及一道模板题 [HNOI2008]玩具装箱
Code
#include <bits/stdc++.h>
#define int long long
using namespace std;
int read(){
int i;
scanf("%lld",&i);
return i;
}
struct Node{
__int128 x,y;
};
bool cmp(Node n1,Node n2,__int128 k){
return n2.y-n1.y<k*(n2.x-n1.x);
}
bool cmp(Node n1,Node n2,Node n3){
return (n2.y-n1.y)*(n3.x-n2.x)<(n3.y-n2.y)*(n2.x-n1.x);
}
Node tb[50010];
int n,L;
int pre[50010];
int key=0,li=0;
signed main(){
n=read(),L=read()+1;
for(int i=1;i<=n;i++)pre[i]=read()+pre[i-1];
tb[0]={L,L*L};
__int128 ans=0;
for(__int128 i=1,k,b;i<=n;i++){
k=2*(pre[i]+i),b=-(pre[i]+i)*(pre[i]+i);
// printf("%lld %lld ",(int)k,(int)b);
while(key<li&&cmp(tb[key],tb[key+1],k))key++;
ans=tb[key].y-k*tb[key].x-b;
Node now={pre[i]+i+L,(pre[i]+i+L)*(pre[i]+i+L)+ans};
// printf("%lld %lld %lld\n",(int)now.x,(int)now.y,(int)ans);
while(li&&!cmp(tb[li-1],tb[li],now))li--;
tb[++li]=now;
}
printf("%lld",(int)ans);
}
时间复杂度\(O(n)\)。
$我们深知自己的平凡,而行,于此途中$
浙公网安备 33010602011771号