[NOI2007]货币兑换 题解

[NOI2007]货币兑换 题解

Problem

​ 初始时有\(m\)的本金,已经知道之后\(n\)天内每一天\(A\)券与\(B\)券的价值,每一天可以买入或卖出金券。

​ 卖出金券方法如下:自己提供一个\([0,100]\)内的实数\(OP\),意义为将\(OP\%\)\(A\)券和\(OP\%\)\(B\)券卖出。

​ 买入金券方法如下:每一天给定一个\(R\),自己给出一个买入的金额\(IP\),满足买入\(A\)券与\(B\)券数量之比为\(R\),总价值为\(OP\)

​ 求\(n\)天后拥有的最大总价值。

Solution

​ 首先不难想到一个这样的策略,卖出金券时,一定全部卖出是最优的,这个感觉比较显然,有了这个,\(OP\)的限制就不存在了。

​ 考虑DP,我们可以得到一个这样的转移方程:

\(dp_i=\max(dp_{i-1},\max\limits_{j=0}^{i-1}\{A_i \times X_j+B_i \times Y_j\})\),其中\(X_i=\frac{R_i \times dp_i}{R_i \times A_i+B_i},Y_i=\frac{dp_i}{R_i \times A_i+B_i}\)

​ 表示第\(i\)天不操作,或者把第\(j\)天买入的金券全部卖出的最大价值。这是一个\(O(n^2)\)的算法,能拿到60pts。

​ 考虑如何去优化,看见式子中含与\(i,j\)有关部分之积\(A_i \times dp_j\),发现这是一个典型的斜率优化。

​ 忽略掉\(\max\)符号,改写一下式子:\(Y_j=-\frac{A_i}{B_i}X_j+\frac{dp_i}{B_i}\),斜率已知,目标是最大化截距,可以维护一个上凸壳。

​ 但是,我们发现每一次加入的点\((X_i,Y_i)\)无论在横坐标上还是纵坐标上都不单调,无法简单的用单调队列维护,于是考虑用平衡树动态维护凸壳,来实现在任意位置插入及删除,总复杂度\(O(nlogn)\)

Code

#include<bits/stdc++.h>
#define eps 1e-8
#define DB double
using namespace std;
int n,m;
DB dp[100005];
DB A[100005],B[100005],R[100005],X[100005],Y[100005];

struct Splay{

    int root;

    DB Lk[100005],Rk[100005];

    int f[100005],son[100005][2];

    inline int which(int x){
        return son[f[x]][1]==x;
    }
    
    inline DB slope(int a,int b){
        if(abs(X[a]-X[b])<eps)
            return -DBL_MAX;
        return (Y[a]-Y[b])/(X[a]-X[b]);
    }
    
    inline void rotate(int x,int &goal){
        int fx=f[x],gx=f[fx],d=which(x);
        if(fx==goal)
            goal=x;
        else
            son[gx][which(fx)]=x;
        son[fx][d]=son[x][d^1];son[x][d^1]=fx;
        f[son[fx][d]]=fx;f[fx]=x;f[x]=gx;
        return;
    }

    inline void splay(int x,int &goal){
        while(x!=goal){
            int fx=f[x];
            if(fx!=goal)
                rotate(which(fx)==which(x)?fx:x,goal);
            rotate(x,goal);
        }
        return;
    }

    void insert(int x){
        if(!root){
            root=x;
            return;
        }
        int now=root,fx=0;
        while(now){
            fx=now;
            now=son[now][X[x]>X[now]+eps];
            if(!now){
                f[x]=fx;
                son[fx][X[x]>X[fx]+eps]=x;
            }
        }   
        return;
    }

    int findpre(int x){
        int now=son[x][0],ans=now;
        while(now){
            if(Lk[now]>=slope(x,now)+eps){
                ans=now;
                now=son[now][1];
            }
            else
                now=son[now][0];
        }
        return ans;
    } 

    int findsuf(int x){
        int now=son[x][1],ans=now;
        while(now){
            if(Rk[now]+eps<=slope(x,now)){
                ans=now;
                now=son[now][0];
            }
            else
                now=son[now][1];
        }
        return ans;
    }

    void Add(int x){
        splay(x,root);
        if(son[x][0]){
            int now=findpre(x);
            Lk[x]=Rk[now]=slope(x,now);
            splay(now,son[x][0]);
            son[now][1]=0;
        }   
        else
            Lk[x]=+DBL_MAX;
        if(son[x][1]){
            int now=findsuf(x);
            Rk[x]=Lk[now]=slope(x,now);
            splay(now,son[x][1]);
            son[now][0]=0;
        }
        else
            Rk[x]=-DBL_MAX;
        if(Lk[x]+eps<=Rk[x]){
            root=son[x][0];
            f[son[x][0]]=0;
            f[son[x][1]]=root;
            son[root][1]=son[x][1];
            Rk[root]=Lk[son[root][1]]=slope(root,son[root][1]);
        }
        return;
    }

    int find(DB k){
        int now=root;
        while(now){
            if(Rk[now]+eps<=k&&k+eps<=Lk[now])
                return now;
            else if(k<Rk[now]+eps)
                now=son[now][1];
            else if(k+eps>Lk[now])
                now=son[now][0];
        }
        return 0;
    } 

}T;

int main(){
    
    scanf("%d",&n);
    scanf("%d",&m);

    for(register int i=1;i<=n;++i){
        scanf("%lf",&A[i]);
        scanf("%lf",&B[i]);
        scanf("%lf",&R[i]);
    }

    dp[0]=m;
    for(register int i=1;i<=n;++i){
        int j=T.find(-A[i]/B[i]);
        dp[i]=max(dp[i-1],A[i]*X[j]+B[i]*Y[j]);
        Y[i]=dp[i]/(R[i]*A[i]+B[i]);X[i]=Y[i]*R[i];
        T.insert(i);T.Add(i);
    }

    printf("%0.3lf\n",dp[n]);

    return 0;
}
posted @ 2020-09-22 20:48  zjy123456  阅读(219)  评论(0编辑  收藏  举报