特性: \(\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)\)
可以发现,所有可能的最优决策点必定在平面点集的凸包上,换句话说不在凸包上的点一定不是最优决策点。
根据点集的特征,我们需要维护一个右下凸包或左上凸包
根据直线斜率和数据点分布的特征,我们可以分为三种情况。
决策直线的斜率与二元组的横坐标同时满足单调性
斜率是单调的,决策点必然在凸包上单调移动
维护一个单调队列即可,步骤如下
- 队头后移,找到决策点 \(j\)
- 计算 \(f(i)\)
- 计算 \(x(i),y(i)\) ,加入队尾,更新凸壳
- 时间复杂度 \(O(n)\)
二元组的横坐标满足单调性
同样维护凸壳,查询时用二分找到最接近的斜率
时间复杂度为 \(O(n\log n)\)
不满足任何特性
也要维护凸壳,但是需要维护时要验证凸性,接着拼接
一次最坏 \(O(n)\) ,相当于没有优化
-
允许离线:可以用 cdq 分治简单地处理,\(O(n\log n)\)
-
强制在线:只能使用平衡树来维护,复杂度 \(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\) ,有
设 \(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]);
}