【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;
}

原文地址:https://www.cnblogs.com/TSHugh/p/8168851.html

时间: 2024-10-15 19:57:14

【BZOJ 1492】 [NOI2007]货币兑换Cash 斜率优化DP的相关文章

BZOJ 1492: [NOI2007]货币兑换Cash( dp + 平衡树 )

dp(i) = max(dp(i-1), x[j]*a[i]+y[j]*b[i]), 0<j<i. x, y表示某天拥有的最多钱去买金券, 金券a和金券b的数量. 然后就很明显了...平衡树维护上凸壳, 询问时就在凸壳上二分...时间复杂度O(NlogN) ----------------------------------------------------------------------------------------------- #include<cmath> #i

[BZOJ1492][NOI2007]货币兑换Cash(斜率优化+CDQ分治)

1492: [NOI2007]货币兑换Cash Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 5838  Solved: 2345[Submit][Status][Discuss] Description 小Y最近在一家金券交易所工作.该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和 B纪念券(以下 简称B券).每个持有金券的顾客都有一个自己的帐户.金券的数目可以是一个实数.每天随着市场的起伏波动, 两种金券都有自己当时的价值,即每一单位金

[BZOJ1492] [NOI2007]货币兑换Cash 斜率优化+cdq/平衡树维护凸包

1492: [NOI2007]货币兑换Cash Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 5907  Solved: 2377[Submit][Status][Discuss] Description 小Y最近在一家金券交易所工作.该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和 B纪念券(以下 简称B券).每个持有金券的顾客都有一个自己的帐户.金券的数目可以是一个实数.每天随着市场的起伏波动, 两种金券都有自己当时的价值,即每一单位金

【BZOJ1492】[NOI2007]货币兑换Cash 斜率优化+cdq分治

[BZOJ10492][NOI2007]货币兑换Cash Description 小Y最近在一家金券交易所工作.该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和 B纪念券(以下简称B券).每个持有金券的顾客都有一个自己的帐户.金券的数目可以是一个实数.每天随着市场的起伏波动,两种金券都有自己当时的价值,即每一单位金券当天可以兑换的人民币数目.我们记录第 K 天中 A券 和 B券 的价值分别为 AK 和 BK(元/单位金券).为了方便顾客,金券交易所提供了一种非常方便的交易方式:比例交易

[BZOJ 1492][NOI2007]货币兑换Cash(CDQ分治+斜率优化Dp)

Description 小Y最近在一家金券交易所工作.该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和 B纪念券(以下 简称B券).每个持有金券的顾客都有一个自己的帐户.金券的数目可以是一个实数.每天随着市场的起伏波动, 两种金券都有自己当时的价值,即每一单位金券当天可以兑换的人民币数目.我们记录第 K 天中 A券 和 B券 的 价值分别为 AK 和 BK(元/单位金券).为了方便顾客,金券交易所提供了一种非常方便的交易方式:比例交易法 .比例交易法分为两个方面:(a)卖出金券:顾客提

bzoj 1492 [NOI2007]货币兑换Cash(斜率dp+cdq分治)

Description Input 第一行两个正整数N.S,分别表示小Y 能预知的天数以及初始时拥有的钱数. 接下来N 行,第K 行三个实数AK.BK.RateK,意义如题目中所述 Output 只有一个实数MaxProfit,表示第N 天的操作结束时能够获得的最大的金钱 数目.答案保留3 位小数. Sample Input 3 100 1 1 1 1 2 2 2 2 3 Sample Output 225.000 HINT 测试数据设计使得精度误差不会超过10-7.对于40%的测试数据,满足N

bzoj 1492: [NOI2007]货币兑换Cash

Description 小Y最近在一家金券交易所工作.该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和 B纪念券(以下 简称B券).每个持有金券的顾客都有一个自己的帐户.金券的数目可以是一个实数.每天随着市场的起伏波动, 两种金券都有自己当时的价值,即每一单位金券当天可以兑换的人民币数目.我们记录第 K 天中 A券 和 B券 的 价值分别为 AK 和 BK(元/单位金券).为了方便顾客,金券交易所提供了一种非常方便的交易方式:比例交易法 .比例交易法分为两个方面:(a)卖出金券:顾客提

BZOJ 1096 [ZJOI2007]仓库建设 斜率优化dp

1096: [ZJOI2007]仓库建设 Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://www.lydsy.com/JudgeOnline/problem.php?id=1096 Description L公司有N个工厂,由高到底分布在一座山上.如图所示,工厂1在山顶,工厂N在山脚. 由于这座山处于高原内陆地区(干燥少雨),L公司一般把产品直接堆放在露天,以节省费用.突然有一天,L公司的总裁L先生接到气象部门的电话,被告知三天之后将有一场

BZOJ 1096 ZJOI2007 仓库设计 斜率优化dp

太高兴了,这是我第一次自己独立思考的斜率优化dp,从头到尾都是自己想的.(相信自己,能行的,不过也做了40分钟了). 这道题目还好吧! 看到之后第一反应是想设从工厂0运到工厂i 总共需要 tot[i] 的费用, 用 p[i] 表示从山顶到工厂 i 总共的产品数, 再用 x[i] 表示从工厂0到工厂 i 的距离, 那么状态转移方程就是 f[i] = min{f[j] + tot[i] - tot[j] - p[j] * (x[i] - x[j] ) + c[i] } ,很明显由于数据有 n <=