【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)) typedefdouble 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; inlinevoid 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; }
【BZOJ 1492】 [NOI2007]貨幣兌換Cash 斜率優化DP