斜率优化入门

斜率优化入门

对于两个\(dp[i]\)的决策点\(j_1,j_2\),满足当时\(\frac{Y(j_2)-Y(j_1)}{X(j_2)-X(j_1)}\leq S_ij_2\)\(j_1\)更优,则可以维护下凸壳来去除一些一定不优的决策点

可以证明,当\(\frac{Y(j_2)-Y(j_1)}{X(j_2)-X(j_1)}\leq S_i\)\(j_2\)\(j_1\) 更优,则上凸壳一定不优,维护下凸壳

反之,当\(\frac{Y(j_2)-Y(j_1)}{X(j_2)-X(j_1)}\geq S_i\)\(j_2\)\(j_1\) 更优则维护上凸壳

然后当\(S_i\)不具有单调性的时候可以二分出最优的斜率

\(S_i\)具有单调性时有决策单调性,用单调队列维护答案

看某位大佬博客看到的总结:

DALAO's 总结

  1. 写出 \(dp\) 方程后,要先判断能不能使用斜优,即是否存在 \(function(i)∗function(j)\)的项。

  2. 通过大小于符号或者 \(b\)\(dp[i]\) 的符号结合题目要求 \((min/max)\) 判断是上凸包还是下凸包,否则死活求不出答案。

  3. 单调性出锅 1 号)将方程变为\(\frac{Y(j_2)-Y(j_1)}{X(j_2)-X(j_1)}\leq S_i\)或者 \(\frac{Y(j_2)-Y(j_1)}{X(j_2)-X(j_1)}\geq S_i\) 或者 \(kx+b=y\)的形式,变化要遵循之前提到的原则,尤其是 \(X\) 表达式的单调性,结合图形会更好理解。如果 \(X\) 不单调,那么需要用到 CDQ 分治或者平衡树维护凸包。

  4. 单调性出锅 2 号)注意是否具有决策单调性,有时候打表只能得到片面的情况。当斜率不是单调递增时该怎么办?由于我们不知道什么时候会在什么地方取得最优决策点,所以必须要保留整个凸包以确保决策有完整的选择空间,查找答案就只能二分了,而不能直接取队首,比如这道题 [任务安排 33 Loj10186 BZOJ2726就不满足,其证明放在后面。

  5. 单调性出锅 3 号)当 \(X\)严格递增时,那么在求斜率时可能会出现 \(X(j_1)==X(j_2)\) 的情况,最好是写成这样的形式:return \(Y(j)⩾Y(i)?inf:−inf\),而不要直接 return inf 或者 −inf,在某些题中情况较复杂,如果不小心画错了图,返回了一个错误的极值就完了,而且这种错误只用简单数据还很难查出来。

  6. 队列初始化要塞入一个点 \(P(0)\),还是以 玩具装箱 toy[P3195] 为例,塞入 \(P(S[0],dp[0]+(S[0]+L)^2)\)\(P(0,0)\),其代表的决策点为 \(0\)

  7. 手写队列初始化是 \(h=1,t=0\) 由于塞了初始点导致 \(t\)\(1\)

  8. 手写队列判断是否为空是 \({h⩽t}\),而出入队判断时都需要有至少 \(2\) 两个元素才能进行操作。所以应是 \({h<t}\)

  9. 计算斜率可能会因为向下取整而出现误差,所以 \(slope\) 函数最好设为 \(long\) \(double\)

  10. 有可能会有一部分的 \(dp\) 初始值无法转移过来,需要手动提前弄一下,例如 摆渡车 [P5017]

  11. 在比较两个斜率时,尽量写上等于,即 \(“⩽”,“⩾”\) 而不是 \(“<”,“>”\)。这样写对于去重有奇效(有重点时会导致斜率分母出锅),但不要以为这样就可以完全去重,因为要考虑的情况非常复杂,所以还是应该加上 5 中提到的特判,万无一失。

  12. 判断斜率大小时尽量把除转换成乘,防止爆精度

BOSS题

这题插入的点不具有单调性,需要用平衡树维护,非常🤢

splay的码可能过一段时间就不知道怎么搞了,准备省选前复习此题的cdq做法

/*
	writer:TLE_AUTUMATIC 2019/10/15 8:30:25 
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,s;
const int N = 300001;
inline int read(){
	int x;scanf("%d",&x);return x;
} 
const double eps=1e-15; //精度,尽量开小 ,但也不要太小,一般 -12 ~ -15 
const double inf = 19260817019219.1;
namespace SPLAY{
	int cnt,fa[N],ch[N][2];double x[N],y[N],lk[N],rk[N];int rt;
	inline int get(int x){
		return ch[fa[x]][1]==x;
	}
	inline void rotate(int x){
		int f=fa[x],g=fa[f];
		int kx=get(x),kf=get(f);
		ch[f][kx]=ch[x][kx^1] , fa[ch[x][kx^1]]=f;
		ch[x][kx^1]=f , fa[f]=x;
		fa[x]=g;
		if(g) ch[g][kf]=x;
	}
	inline void splay(int x,int &v){
		int f,to=fa[v];
		while(fa[x]!=to){ 
			f=fa[x];
			if(fa[f]!=to){
				rotate(get(x)==get(f)?f:x);
			}
			rotate(x);
		} 
		v=x;
	}  //以上是基础操作,但是要注意此处splay是把x变到v的位置 
	inline double fb(double now){
		return now>0?now:-now;
	}//fabs 
	inline double slope(int i,int j){
		return (fb(x[i]-x[j])<=eps?-inf:(y[i]-y[j])/(x[i]-x[j]));
	}//两点斜率 
	inline void insert(int &now,double nx,double ny,int lst){
		if(!now){
			now=++cnt;x[now]=nx,y[now]=ny,fa[now]=lst;return;  
		}
		insert(ch[now][nx-x[now]>eps],nx,ny,now);
	}//按x的大小插入 
	inline int pre(int now){
		int u=ch[now][0],res=0;
		while(u){
			if(slope(now,u)>=rk[u]-eps) res=u,u=ch[u][0]; //斜率按中序遍历单调递减,此时继续往前走 
			else u=ch[u][1];
		}
		return res;
	}//前驱 
	inline int nex(int now){
		int u=ch[now][1],res=0;
		while(u){
			if(slope(now,u)<=lk[u]+eps) res=u,u=ch[u][1];
			else u=ch[u][0];
		}
		return res;
	}//后继 
	inline int getl(int now){
		while(ch[now][0]) now=ch[now][0];
		return now;
	}//最左边 
	inline int getr(int now){
		while(ch[now][1]) now=ch[now][1];
		return now;
	}//最右 
	inline void del(int x){
		if(!ch[x][0]){ 
			int r=getl(ch[x][1]);
			splay(r,ch[x][1]),rt=r;//此时r的右子树是整棵树,因为没有比r更小的了(除了x) 
			ch[x][1]=fa[rt]=0;//删除整棵树 
			lk[rt]=inf;
		}else if(!ch[x][1]){
			int l=getr(ch[x][0]);  //原理同上  
			splay(l,ch[x][0]),rt=l;
			ch[x][0]=fa[rt]=0;
			rk[rt]=-inf;
		}else{
			int rmin=getl(ch[x][1]),lmax=getr(ch[x][0]);  
			splay(lmax,ch[x][0]);
			splay(rmin,ch[x][1]);
			rt=lmax;ch[rt][1]=rmin;fa[rmin]=rt;
			rk[rt]=lk[rmin]=slope(rt,rmin);//只保留两个点 
		}
	}
	inline void work(int x){
		splay(x,rt);
		if(ch[x][0]){
			int l=pre(x);
			if(l){
				splay(l,ch[x][0]);
				ch[l][1]=fa[ch[l][1]]=0;//删除l的子树 
				rk[l]=lk[x]=slope(l,x); 
			}
			else lk[x]=-inf;
		}else lk[x]=inf;
		if(ch[x][1]){
			int r=nex(x);
			if(r){
				splay(r,ch[x][1]);
				ch[r][0]=fa[ch[r][0]]=0;//删除r的子树 
				lk[r]=rk[x]=slope(r,x); 
			}
			else rk[x]=inf;
		}else rk[x]=-inf;
		if(lk[x]-rk[x]<=eps) del(x);//删除x 
	}
	inline int get_ans(int x,double k){
		if(!x) return 0;
		if(lk[x]+eps>=k&&rk[x]<=k+eps) return x; //找到切点
		if(lk[x]-eps<k) return get_ans(ch[x][0],k); //k>斜率,在左边,斜率单增
		else return get_ans(ch[x][1],k);//k<斜率,在右边,斜率单减 
	}
};
double f[N],A[N],B[N],R[N];
int main(){
	n=read(),scanf("%lf",&f[0]);
	for(int i=1;i<=n;i++){
		scanf("%lf%lf%lf",&A[i],&B[i],&R[i]);
		int res=SPLAY::get_ans(SPLAY::rt,(-A[i]/B[i]));
		double nx=SPLAY::x[res],ny=SPLAY::y[res];
		f[i]=max(f[i-1],A[i]*nx+B[i]*ny);
		ny=f[i]/(A[i]*R[i]+B[i]);
		nx=ny*R[i];
		SPLAY::insert(SPLAY::rt,nx,ny,0);
		SPLAY::work(i);
	}
	printf("%.3lf",f[n]);
	return 0;
} 
posted @ 2019-10-12 17:28  lcyfrog  阅读(288)  评论(0编辑  收藏  举报