斜率优化 dp

特性: \(\text{dp}\) 方程为 \(dp_i=\min_{j=1}^{i-1}{a_i*x(j)+b_i*y(j)}\) ,其中 \(b\) 严格单调递增

\(x(j),y(j)\) 都是能在常数时间通过 \(f_j\) 唯一决定的二元组

斜率优化

以下内容转载于 Bill Yang's Blog

原理

考虑 \(j\) 的转移 \(f_i=a_i*x(j)+b_i*y(j)\) ,以 \(x(j)\) 为横轴, \(y(j)\) 为纵轴建立平面直角坐标系

这样 \(j\) 可以用坐标系上的一个点表示,原方程转化为 \(f_i=\min\{a_i*x+b_i*y\}\)

化为斜截式方程 \(y=-\dfrac{a}{b}x+\dfrac{f(i)}{b}\),若 \(b>0\) ,则目标是最小化纵截距

用斜率 \(-\dfrac{a}{b}\) 一直向上平移,所遇见的第一个点就是最优决策点 \(j\) ,可通过 \(j\) 计算出最优的 \(f(i)\)

可以发现,所有可能的最优决策点必定在平面点集的凸包上,换句话说不在凸包上的点一定不是最优决策点

根据点集的特征,我们需要维护一个右下凸包或左上凸包

根据直线斜率和数据点分布的特征,我们可以分为三种情况。

决策直线的斜率与二元组的横坐标同时满足单调性

斜率是单调的,决策点必然在凸包上单调移动

维护一个单调队列即可,步骤如下

  1. 队头后移,找到决策点 \(j\)
  2. 计算 \(f(i)\)
  3. 计算 \(x(i),y(i)\) ,加入队尾,更新凸壳
  4. 时间复杂度 \(O(n)\)
二元组的横坐标满足单调性

同样维护凸壳,查询时用二分找到最接近的斜率

时间复杂度为 \(O(n\log n)\)

不满足任何特性

也要维护凸壳,但是需要维护时要验证凸性,接着拼接

一次最坏 \(O(n)\) ,相当于没有优化

  1. 允许离线:可以用 cdq 分治简单地处理,\(O(n\log n)\)

  2. 强制在线:只能使用平衡树来维护,复杂度 \(O(n\log n)\)

    在维护凸壳时,先将新数据点Splay到根结点,剩下的结点必定分居左子树与右子树。

    然后我们以左子树为例,后序遍历依次查找结点,直到找到一个满足凸性的结点,

    将这个结点Splay到根结点的左儿子,然后我们删掉这个结点的右子树即可

    可以保证复杂度,但是实现巨麻烦

玩具装箱

\(S_n=\sum_{i=1}^n C_i+1\) ,容易想到 \(dp_i=\min_{j=1}^{i}dp_j+(S_i-S_j-1-L)^2\)

如果 \(L\) 提前加 1 再去掉 \(\min\),则 \(dp_i=dp_j+(S_i-S_j-L)^2\)

化简得 \(dp_i=dp_j+(S_i)^2-2S_iS_j-2S_iL+(S_j+L)^2\)


设有 \(j,k\) 两个决策点,且 \(j\) 优于 \(k\) ,有

\[\begin{aligned} dp_j+(S_i)^2-2S_iS_j-2S_iL+(S_j+L)^2 & \le dp_k+(S_i)^2-2S_iS_k-2S_iL+(S_k+L)^2 \\ dp_j-2S_iS_j+(S_j+L)^2 & \le dp_k-2S_iS_k+(S_k+L)^2 \\ dp_j-dp_k+(S_j+L)^2-(S_k+L)^2 & \le 2S_i(S_j-S_k) \\ \dfrac{dp_j-dp_k+(S_j+L)^2-(S_k+L)^2}{S_j-S_k} & \le 2S_i \end{aligned} \]

\(Y(i)=dp_i+(S_i+L)^2,X(i)=S_i\) ,则 \(\dfrac{Y(j)-Y(k)}{X(j)-X(k)} \le 2S_i\)

好像式子左边是一个斜率公式 QwQ

也就是说,要尽量把式子化成斜率

因为 \(X(i),Y(i),2S_i\) 单调递增,所以直接单调队列即可

#include<bits/stdc++.h>
using namespace std;
typedef long double LD;
typedef long long LL;
const LL N=50005;
LL n,q[N],hd,tl;
LL L,S[N],dp[N];
inline LL X(int i) { return S[i]; }
inline LL Y(int i) { return dp[i]+(S[i]
	return (LD)(Y(j)-Y(i))/(X(j)-X(i));
}
int main() {
	scanf("%lld%lld",&n,&L),++L;
	for(int i=1;i<=n;i++)scanf("%lld",&S[i]),S[i]+=S[i-1]+1LL;
	hd=1,q[++tl]=0;
	for(int i=1,j=0;i<=n;i++) {
		while(hd<tl && slope(q[hd],q[hd+1])<=2*S[i])++hd;
		j=q[hd],dp[i]=dp[j]+(S[i]-S[j]-L)*(S[i]-S[j]-L);
		while(hd<tl && slope(q[tl-1],q[tl])>=slope(q[tl],i))--tl;
		q[++tl]=i;
	}
	printf("%lld",dp[n]);
}
原文地址:https://www.cnblogs.com/KonjakLAF/p/14513917.html