【BZOJ 1492】 [NOI2007]货币兑换Cash 斜率优化DP
先说一下斜率优化:这是一种经典的dp优化,是OI中利用数形结合的思想解决问题的典范,通常用于优化dp,有时候其他的一些决策优化也会用到,看待他的角度一般有两种,但均将决策看为二维坐标系上的点,并转化为维护凸壳,一种根据两点的斜率与某一常数的大小关系推断二者的优劣,一种将转移方程化为相关直线方程,通过取得最大(小)截距来求最优解。关于其实现方法上,当点的x坐标单调时,可依据比较常数是否单调选择单调队列或单调栈,而当其x坐标不单调时常常使用CDQ分治或平衡树来实现。
千万别用替罪羊来写动态凸壳!!!
用平衡树来写动态凸壳的话,很容易想到的是维护凸壳点集并使x坐标单调,那么这个时候你不仅得到了单调的x坐标,点与点之间的斜率也就是单调的了,这个时候你就可以给每个点再维护两个值:他与他在凸壳上左边的点连成的线段的斜率和他与他在凸壳上右边的点连成的线段的斜率(边界设为正无穷和负无穷),维护了这两个值你就可以直接在二叉树上查找最优决策点,而不用二分。当插入一个点的时候,你可以在这个点两边暴力pop,这样均摊nlogn,也可以直接在这个点两边进行二叉查找这样严格nlogn,但是常数相对较小。用splay或者Treap(无旋有旋都可以,只是常数差异)会很优秀,但是用替罪羊的话,呵呵.......不仅码量爆炸,而且各种操作都不是很好实现,就拿维护上面说的两个值来讲,如果你的替罪羊删除用的是标记,那么你就要**了.....因为废点在二叉查找的时候也需要有指示作用,但是你不能把他放入凸壳来维护,所以你还要维护一下盖住他的线段的斜率......(可能写替罪羊的时候把维护的信息统一改为前驱实点和后继实点会好很多)
下面是巨丑巨慢的替罪羊程序.....
#include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define ft first #define sd second #define mmp(a,b) (std::make_pair(a,b)) #define get_k(x,y) (((x)->b-(y)->b)/((x)->a-(y)->a)) typedef double db; typedef std::pair<db,db> pdd; const int N=100010; const db eps=1e-8; const db Inf=1./0.; const db oo=-1./0.; db A[N],B[N],R[N],f[N]; int n,s; namespace SGT{ const db alpha=0.75; struct ScapeGoat_Tree{ ScapeGoat_Tree *ch[2],*zz; int size,cover,ex; db a,b,lk,rk; inline void pushup(){ size=ch[0]->size+ch[1]->size+ex; cover=ch[0]->cover+ch[1]->cover+1; } inline void update(); inline bool isbad(){ return cover*alpha+5<ch[0]->cover||cover*alpha+5<ch[1]->cover; } }*root,*null,node[N],*list[N]; int len,sz; inline void ScapeGoat_Tree:: update(){ if(ch[0]->zz!=null)return void(zz=ch[0]->zz); if(ex)return void(zz=this); zz=ch[1]->zz; } inline void Init(){ null=node+(sz++); null->ch[0]=null->ch[1]=null->zz=null; root=null; } inline void travel(ScapeGoat_Tree *p){ if(p==null)return; travel(p->ch[0]); if(p->ex)list[++len]=p; travel(p->ch[1]); } inline ScapeGoat_Tree *divide(int l,int r){ if(l>r)return null; int mid=(l+r)>>1; list[mid]->ch[0]=divide(l,mid-1); list[mid]->ch[1]=divide(mid+1,r); list[mid]->pushup(); list[mid]->update(); return list[mid]; } inline void rebuild(ScapeGoat_Tree *&p){ len=0,travel(p),p=divide(1,len); } inline int get_rank(db x){ int ret=0; ScapeGoat_Tree *p=root; while(p!=null) if(p->a<x+eps) ret+=p->ch[0]->size+p->ex,p=p->ch[1]; else p=p->ch[0]; return ret; } inline ScapeGoat_Tree *get_kth(int k){ ScapeGoat_Tree *p=root; while(true) if(p->ex&&p->ch[0]->size+p->ex==k) return p; else if(p->ch[0]->size>=k) p=p->ch[0]; else k-=p->ch[0]->size+p->ex,p=p->ch[1]; } inline ScapeGoat_Tree **insert(ScapeGoat_Tree *&p,db x,db y){ if(p==null){ p=node+(sz++); p->ch[0]=p->ch[1]=null; p->size=p->cover=p->ex=1; p->a=x,p->b=y,p->zz=p; return &null; } ++p->size,++p->cover; ScapeGoat_Tree **ret=insert(p->ch[p->a<x],x,y); if(p->isbad())ret=&p; p->update(); return ret; } inline void Insert(db x,db y){ int k=get_rank(x); ScapeGoat_Tree *p1,*p2,*p3; ScapeGoat_Tree **p=insert(root,x,y); p2=node+(sz-1); if(k!=0){ p1=get_kth(k); p1->rk=p2->lk=get_k(p1,p2); }else p2->lk=Inf; if(k+2<=root->size){ p3=get_kth(k+2); p2->rk=p3->lk=get_k(p2,p3); }else p2->rk=oo; if(*p!=null)rebuild(*p); } inline void del(ScapeGoat_Tree *p,int k){ --p->size; if(p->ex&&p->ch[0]->size+p->ex==k){ p->ex=0,p->update(); return; } if(p->ch[0]->size>=k)del(p->ch[0],k); else del(p->ch[1],k-p->ch[0]->size-p->ex); p->update(); } inline void Del(ScapeGoat_Tree *p){ int k=get_rank(p->a); del(root,k); ScapeGoat_Tree *p1,*p2; if(root->size!=0){ if(k==1){ p2=get_kth(k); p2->lk=Inf; }else if(k>root->size){ p1=get_kth(k-1); p1->rk=oo; }else{ p2=get_kth(k); p1=get_kth(k-1); p1->rk=p2->lk=get_k(p1,p2); } } if(root->size+5<root->cover*alpha)rebuild(root); } inline void Del(int k){ del(root,k); ScapeGoat_Tree *p1,*p2; if(root->size!=0){ if(k==1){ p2=get_kth(k); p2->lk=Inf; }else if(k>root->size){ p1=get_kth(k-1); p1->rk=oo; }else{ p2=get_kth(k); p1=get_kth(k-1); p1->rk=p2->lk=get_k(p1,p2); } } if(root->size+5<root->cover*alpha)rebuild(root); } inline bool die(int k){ ScapeGoat_Tree *p=get_kth(k); return p->lk<p->rk+eps; } inline void Ins(db x,db y){ int k=get_rank(x); if(k==0){ Insert(x,y),++k; }else{ ScapeGoat_Tree *p=get_kth(k); if(fabs(p->a-x)<eps) p->b=std::max(p->b,y); else Insert(x,y),++k; } if(die(k))return void(Del(k)); while(k!=root->size&&die(k+1))Del(k+1); while(k!=1&&die(k-1))Del(k-1),--k; } inline pdd query(ScapeGoat_Tree *p,db k){ if(p->ex&&k<=p->lk+eps&&eps+k>=p->rk) return mmp(p->a,p->b); if((p->ex&&k>p->lk+eps)||(p->ex==0&&(p->ch[1]->size==0||p->ch[1]->zz->lk<k))) return query(p->ch[0],k); else return query(p->ch[1],k); } } int main(){ scanf("%d%d",&n,&s); int i;SGT::Init(); for(i=1;i<=n;++i) scanf("%lf%lf%lf",&A[i],&B[i],&R[i]); f[1]=s; db y=s/(R[1]*A[1]+B[1]),x=y*R[1]; SGT::Insert(x,y); pdd ret; for(i=2;i<=n;++i){ ret=SGT::query(SGT::root,-A[i]/B[i]); f[i]=B[i]*ret.sd+A[i]*ret.ft; f[i]=std::max(f[i],f[i-1]); y=f[i]/(R[i]*A[i]+B[i]),x=y*R[i]; SGT::Ins(x,y); } printf("%.3f",f[n]); return 0; }
苟利国家生死以, 岂因祸福避趋之。