[BZOJ1492]-[NOI2007]货币兑换Cash-斜率优化+Splay凸壳

UPD 2018 2.14 发现me的dp方程有问题,已更正

说在前面

本来想写着一道题,把斜率优化再练练,顺便也写一写动态插入的凸包
然后me写了一天!一天!??


题目

BZOJ1492传送门
这题面太长= =
幸好不是权限题,看题可以进传送门


解法

首先,题目中提到的一天卖出OP%的条件,其实并没有什么用。因为策略肯定是要么把券卖完,要么把钱全部换成券
对于卖出操作来说:如果有一天最优,那么肯定是全部卖出去(同样的,如果不优,一定不卖)。买入操作同理。

因为一天可以进行多次操作,于是对于每一天我们拥有的价值,都可以换算成 钱数 来计量。(相当于是把券换算成钱来计量,因为同时记录 手头拥有的券和钱数 会很复杂,所以要避免)

于是定义dp[i]表示第i天拥有的钱数,转移: dp[i]=max(cntAA[i]+cntBB[i],dp[i1]) d p [ i ] = m a x ( c n t A ∗ A [ i ] + c n t B ∗ B [ i ] , d p [ i − 1 ] )
又因为 cntA:cntB=Rate[j] c n t A : c n t B = R a t e [ j ] cntAA[j]+cntBB[j]=dp[j] c n t A ∗ A [ j ] + c n t B ∗ B [ j ] = d p [ j ] ,解出 cntB=dp[j]A[j]Rate[j]+B[j],cntA=cntBRate[j] c n t B = d p [ j ] A [ j ] ∗ R a t e [ j ] + B [ j ] , c n t A = c n t B ∗ R a t e [ j ]


于是dp式是这样的: dp[i]=max(dp[j]Rate[j]A[j]Rate[j]+B[j]A[i]+dp[j]A[j]Rate[j]+B[j]B[i],dp[i1]) d p [ i ] = m a x ( d p [ j ] ∗ R a t e [ j ] A [ j ] ∗ R a t e [ j ] + B [ j ] ∗ A [ i ] + d p [ j ] A [ j ] ∗ R a t e [ j ] + B [ j ] ∗ B [ i ] , d p [ i − 1 ] )
观察可以发现,这个转移是形如 C=Ax+By C = A x + B y 的,符合斜率优化,于是令 x=dp[j]Rate[j]A[j]Rate[j]+B[j],y=dp[j]A[j]Rate[j]+B[j] x = d p [ j ] ∗ R a t e [ j ] A [ j ] ∗ R a t e [ j ] + B [ j ] , y = d p [ j ] A [ j ] ∗ R a t e [ j ] + B [ j ] 之后,就可以把式子写成这样: y=A[i]B[i]x+CB[i] y = − A [ i ] B [ i ] ∗ x + C B [ i ]
这确实是一个斜率优化的式子,然而这个式子和普通的斜率优化有一些区别,因为这里斜率和横坐标都不单调(注:斜率和横坐标都单调就可以使用单调队列,斜率和纵坐标都单调就可以使用单调栈,仅仅斜率单调可以使用二分来判定超越时间)。于是这里就需要维护支持”动态插入,查询对于某个斜率的切点”的 上凸壳


关于维护,在线可以使用Splay,离线使用CDQ。me选用的是Splay在线维护,按照横坐标排序,然后维护上凸壳。每次插入一个点的时候,要么这个点在凸包内部(判断一下,然后不插入),或者这个点在凸包外部并使一些点失效,失效的点一定是一段连续的区间,因此用Splay提取出来删去即可。另外,在每个点上维护leftK和rightK,表示和左右相邻的点的斜率,最左边的leftK需要设为inf,而最右边的rightK需要设为-inf,不然会出现奇奇怪怪的错误。

维护凸包有两种维护方法,一种是通过极角序维护完整凸包,另一种是分别维护上下凸壳。但是极角序只能维护凸包,而不能维护凸壳,因此这道题 使用横坐标有序 维护上凸包。不然会出现下图所示,这五个点对于原点是 第一象限凸包 的一部分,但是却不是 第一象限凸壳 ,因为最下方的点可以上面的点完全覆盖

这里写图片描述


下面是自带大常数的代码

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;

int N ;
double dp[100005] , A[100005] , B[100005] , R[100005] , eps = 1e-9 ;
struct Points{
    double x , y ;
    Points(){} ;
    Points( const double &x_ , const double &y_ ) :
        x( x_ ) , y( y_ ) {};
    double b( const double &k ){ return y - k * x ; }
} ;
struct Node{
    Points p ;
    double lk , rk ;
    Node *fa , *ch[2] ;
} *root ;

inline short cmp( const double &a ){
    if( a < eps && a > -eps ) return 0 ;
    return a > 0 ? 1 : -1 ;
}

double K( const Points &i , const Points &j ){
    if( cmp( i.x - j.x ) == 0 ) return -1e18 ;
    return ( i.y - j.y ) / ( i.x - j.x ) ;
}

Node *newNode( Node *fa , Node *&nd , const Points &p_ ){
    nd = new Node() ;
    nd->p = p_ ; nd->fa = fa ;
    nd->ch[0] = nd->ch[1] = NULL ;
    return nd ;
}

Node* Insert( Node *fa , Node *&nd , const Points &tmp ){
    if( !nd ) return newNode( fa , nd , tmp ) ;
    short p = cmp( nd->p.x - tmp.x ) < 0 ;
    return Insert( nd , nd->ch[p] , tmp ) ;
}

void Rotate( Node *nd , int aim ){
    Node *x = nd->ch[aim] ;
    if( nd->fa ){
        if( nd->fa->ch[0] == nd ) nd->fa->ch[0] = x ;
        else if( nd->fa->ch[1] == nd ) nd->fa->ch[1] = x ;
    } x->fa = nd->fa ;
    if( x->ch[aim^1] ){
        x->ch[aim^1]->fa = nd ;
    } nd->ch[aim] = x->ch[aim^1] ;
    x->ch[aim^1] = nd ;
    nd->fa = x ;
}

void Splay( Node *nd , Node *tmp ){
    while( nd->fa != tmp ){
        Node *fa = nd->fa , *gdfa = fa->fa ;
        short pn = ( fa->ch[1] == nd ) , pf ;
        if( gdfa && gdfa != tmp ){
            pf = ( gdfa->ch[1] == fa ) ;
            if( pn == pf ) Rotate( gdfa , pf ) , Rotate( fa , pn ) ;
            else           Rotate( fa , pn ) , Rotate( gdfa , pf ) ;
        } else Rotate( fa , pn ) ;
    }
    if( !tmp ) root = nd ;
}

Node *front( Node *nd , const double &x ){
    Node *rt = NULL ;
    while( nd ){
        short p = cmp( nd->p.x - x ) < 0 ;
        if( p == 1 ) rt = nd ;
        nd = nd->ch[p] ;
    } return rt ;
}

Node *after( Node *nd , const double &x ){
    Node *rt = NULL ;
    while( nd ){
        short p = cmp( nd->p.x - x ) <= 0 ;
        if( p == 0 ) rt = nd ;
        nd = nd->ch[p] ;
    } return rt ;
}

void update( Node *lf , Node *rg ){
    if( !lf ) rg->lk = 1e18 ;
    else if( !rg ) lf->rk = -1e18 ;
    else lf->rk = rg->lk = K( lf->p , rg->p ) ;
}

void Insert( const Points &tmp ){
    Node *fr = front( root , tmp.x ) ;
    Node *af = after( root , tmp.x ) ;
    if( fr && af && K( fr->p , tmp ) <= K( tmp , af->p ) ) return ;

    Node *nd = Insert( NULL , root , tmp ) ;
    update( fr , nd ) ; update( nd , af ) ;
    Splay( nd , NULL ) ;

    if( root->ch[1] ){
        Node *t = root->ch[1] ;
        while( t ){
            if( K( nd->p , t->p ) >= t->rk - eps ) af = t , t = t->ch[0] ;
            else t = t->ch[1] ;
        }
        Splay( af , root ) ; af->ch[0] = NULL ;
        update( nd , af ) ;
    }

    if( root->ch[0] ){
        Node *t = root->ch[0] ;
        while( t ){
            if( K( nd->p , t->p ) <= t->lk + eps ) fr = t , t = t->ch[1] ;
            else t = t->ch[0] ;
        }
        Splay( fr , root ) ; fr->ch[1] = NULL ;
        update( fr , nd ) ;
    }
}

Node *Find( double k ){
    Node *nd = root ;
    while( nd ){
        if( nd->rk > k + eps ) nd = nd->ch[1] ;
        else if( nd->lk + eps < k ) nd = nd->ch[0] ;
        else return nd ;
    }
}

void solve(){
    double tmp = dp[1]/( A[1]*R[1]+B[1] ) ;
    Insert( Points( R[1] * tmp , tmp ) ) ;
    root->lk = 1e18 ; root->rk = -1e18 ;

    for( int i = 2 ; i <= N ; i ++ ){
        double k = -A[i] / B[i] ;
        Points p = Find( k )->p ;
        dp[i] = max( p.x * A[i] + p.y * B[i] , dp[i-1] ) ;

        tmp = dp[i]/( A[i]*R[i]+B[i] ) ;
        Insert( Points( R[i] * tmp , tmp ) ) ;
    }
    printf( "%.3f" , dp[N] ) ;
}

int main(){
    scanf( "%d%lf" , &N , &dp[1] ) ;
    for( int i = 1 ; i <= N ; i ++ )
        scanf( "%lf%lf%lf" , &A[i] , &B[i] , &R[i] ) ;
    solve() ;
}
/*
IN :
10 100  
5.226 5.381 1.73  
5.273 5.899 1.35  
5.275 5.236 1.93  
5.769 5.863 1.78  
5.888 5.064 1.91  
5.464 5.894 1.95  
5.565 5.731 1.97  
5.568 5.305 1.31  
5.639 5.501 1.85  
5.751 5.925 1.14  

OUT :
123.072
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值