【学习记录】斜率优化
记录一下斜率优化算法。
斜率优化,顾名思义就是用数学中函数常用的斜率进行优化。这里拿 DP 来举例说明。
斜率
我们任取两个决策点 \(j_1\) 和 \(j_2\) \((j_1<j_2)\)。我们不妨先设决策点 \(j_2\) 不劣于决策点 \(j_1\)。假如说我们的状态转移方程为
同时,设
(\(a,b,c,d\) 均为常数)
那么显然,这个决策优异性表现为越小越优(因为 \(\min\) 函数)。所以,在任意某个 \(i\) 处,一旦我们最先的关于决策点优秀度的假设成立,那么对于两个决策点,就满足
的关系。展开,得
去掉两边的相同项,得
这时,如果我们变一变形式:
不难发现,此时这个不等式被我们化成了部分形式相仿的一些部分。不妨归纳一下。可设
于是,原不等式就可化为
(其中,\(Z=ai\) 是 \(i\) 阶段定值)
定睛细看,这不就是斜率吗?
优化
既然斜率有了,问题就解决了一半,甚至不止一半。那么,优化当然就是迫在眉睫的事情。
注意到刚刚得出的关于斜率的不等式
倘若我们在平面直角坐标系 \(XOY\) 上任取 \(3\) 个点 \(j_1,j_2,j_3\),不妨设 \(X(j_1)<X(j_2)<X(j_3),k_1>k_2\)。满足坐标 \(\left(X(j),Y(j)\right)\),且 \(j_1,j_2\) 点斜率为 \(k_1\)、\(j_2,j_3\) 点斜率为 \(k_2\)。
我们就有 \(3\) 种情况,分别是
而假设相对优秀情况用 \(v(j)\) 表示,那么以上 \(3\) 种情况对应的比较应该是
还记得我们的目的吗?我们的目的是找到所有决策点 \(j\) 中最优的,也就是说,次优的或更劣的一定不会被我们选中。观察上述决策点规律,不难发现 \(j_2\) 决策点永远都不是最优的,最高只可能是次优情况。回顾前提条件,我们得到了这样一个结论:
当存在 \(3\) 个决策点 \(j_1,j_2,j_3(X(j_1)<X(j_2)<X(j_3))\) 时,若满足 \(k_1>k_2\),则最优决策点只能位于 \(j_1,j_3\)。
也就是说,在真实代码操作过程中,我们可以动态维护 \(3\) 个点,如果斜率出现 \(k_1>k_2\) 的情况,就可以删点。考虑全面、极端的情况。假如已经将所有的中项点(即 \(j_2\))删除完毕,并且创建了一个新的 \(j_1,j_3\) 点连接,得到了新的斜率 \(k_3\)(当然,如果发现 \(j_1\) 或 \(j_3\) 不合法,\(k_3\) 也会被其他斜率代替)。如果 \(k\) 在实数中较全面分布,那么 \(k\) 将一直从 \(-\infty,\dots,p,(0,)\dots ,+\infty\)。在图象上,\(j\) 点将先严格下降,而后严格上升,且严格下降坡度越来越缓,而严格上升坡度越来越陡。不难想象出,这是一个类似于碗状的图形。我们称它为下凸包。
但是,请始终记住,这是查找 \(\min\)。如果查找 \(\max\) 呢?那我们不等式中的 \(\geq\) 就会变成 \(\leq\)。所以,恰好相反,最终的图形是上凸包。
进一步优化
不论是哪个方向的凸包,最优节点都会产生在构成凸包的这些点中。如果想要进一步优化,我们可以给这些凸包上的点再缩圈。这些就要看题目具体本身的东西了。拿题目 \(\text{P3195 [HNOI2008] 玩具装箱}\) 来举例。这道题,我们会发现其在决策过程中满足四边形不等式,换句话说,具有一定的单调性,这样我们只要往一个地方不断推进就行了。在这里,我们采用维护一个双端队列的方式,记录队头为 \(l\),队尾为 \(r\)。对于每个 \(i\) 决策就变成了:
- 从队头 \(l\) 开始,对于队列中每个点 \(Q_j(j\geq l)\),如果该点满足 \(k_1>k_2\),那么就向这个点推进。因为其满足一定的单调性。如果推进不了了,那么 \(Q_{l,l+1,\dots,j-1}\) 就一定劣于 \(Q_j\),那么 \(l \gets j\),停止推进。
 - 更新 \(dp_i\)。
 - 现在,由于加入了一个新点 \(q\),部分队列中的斜率将不再适配目前新点要求。也就是说,加入了新点之后,可能会出现 \(k_1<k_2\) 的情况。这个时候,我们就要继续删点。对于 \(3\) 个元素 \(Q_{r-1},Q_{r},q\),如果说 \(k(Q_{r-1},Q_{r})\geq k(Q_{r},q)\)(这里将斜率相同去重),那么 \(r \gets r-1\);如果恰恰相反,说明此时不需要再进行删点了。直接 \(r \gets r+1\),然后 \(Q_{r} \gets q\)。
 
那么,这个问题就解决了。
(贴一下代码)
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,L;
int c[50010];
int s[50010];
int l=1,r=1;
int q[50010];
int dp[50010];
int X(int j){
	return s[j];
}
int Y(int j){
	return (s[j]+L)*(s[j]+L)+dp[j];
}
inline long double slope(int u,int v){
	return (long double)(Y(v)-Y(u))/(X(v)-X(u));
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>L;
	L++;
	for(int i=1;i<=n;i++){
		cin>>c[i];
		s[i]=c[i]+1+s[i-1];
	}
	for(int i=1;i<=n;i++){
		while(l<r&&slope(q[l],q[l+1])<=2*s[i]){
			l++;
		}
		dp[i]=dp[q[l]]+(s[i]-s[q[l]]-L)*(s[i]-s[q[l]]-L);
		while(l<r&&slope(q[r-1],q[r])>=slope(q[r],i)) r--;
		q[++r]=i;
	}
	cout<<dp[n]<<endl;
	return 0;
}
总结
无论是什么优化,一定都是从最基本的开始推的。如果没有什么优化的思路,不如假设一下,从已知条件(或假设的条件)出发,看看有没有什么特别的性质,看看有没有什么熟悉的形式(例如斜率式),看看能不能把抽象的东西具象化,看看能不能采取大脑更加认同的方式(图形化处理)。也许这样,说不定就给我们的思考做了一次完美的剪枝。
(点个赞再走呗)
                    
                
                
            
        
浙公网安备 33010602011771号