斜率优化入门

斜率优化入门

对于两个\(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

  9. 计算斜率可能会因为向下取整而出现误差,所以 \(slope\) 函数最好设为 \(long\) \(double\)
  10. 有可能会有一部分的 \(dp\) 初始值无法转移过来,需要手动提前弄一下,例如 摆渡车 [P5017]

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

BOSS题

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

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

/*
    writer:TLE_AUTUMATIC 2019/10/15 8:30:25 
*/
#include
#include
#include
#include
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斜率,在左边,斜率单增
        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;
} 

你可能感兴趣的:(斜率优化入门)