• P4027 [NOI2007] 货币兑换


    P4027 [NOI2007] 货币兑换

    题目描述

    小 Y 最近在一家金券交易所工作。该金券交易所只发行交易两种金券:A 纪念券(以下简称 A 券)和 B 纪念券(以下简称 B 券)。每个持有金券的顾客都有一个自己的帐户。金券的数目可以是一个实数。

    每天随着市场的起伏波动,两种金券都有自己当时的价值,即每一单位金券当天可以兑换的人民币数目。我们记录第 K K K 天中 A 券和 B 券的价值分别为 A K A_K AK B K B_K BK(元/单位金券)。

    为了方便顾客,金券交易所提供了一种非常方便的交易方式:比例交易法。

    比例交易法分为两个方面:

    a) 卖出金券:顾客提供一个 [ 0 , 100 ] [0, 100] [0,100] 内的实数 O P OP OP 作为卖出比例,其意义为:将 O P % OP\% OP% 的 A 券和 O P % OP\% OP% 的 B 券以当时的价值兑换为人民币;

    b) 买入金券:顾客支付 I P IP IP 元人民币,交易所将会兑换给用户总价值为 I P IP IP 的金券,并且,满足提供给顾客的 A 券和 B 券的比例在第 K K K 天恰好为 R a t e K \mathrm{Rate}_ K RateK

    例如,假定接下来 3 3 3 天内的 A K , B K , R a t e K A_K,B_K,\mathrm{Rate}_ K AK,BK,RateK 的变化分别为:

    时间 A K A_K AK B K B_K BK R a t e K \mathrm{Rate}_ K RateK
    第一天 1 1 1 1 1 1 1 1 1
    第二天 1 1 1 2 2 2 2 2 2
    第三天 2 2 2 2 2 2 3 3 3

    假定在第一天时,用户手中有 100 100 100 元人民币但是没有任何金券。

    用户可以执行以下的操作:

    时间用户操作人民币(元)A 券的数量B 券的数量
    开户 100 100 100 0 0 0 0 0 0
    第一天买入 100 100 100 0 0 0 50 50 50 50 50 50
    第二天卖出 50 % 50\% 50% 75 75 75 25 25 25 25 25 25
    第二天买入 60 60 60 15 15 15 55 55 55 40 40 40
    第三天卖出 100 % 100\% 100% 205 205 205 0 0 0 0 0 0

    注意到,同一天内可以进行多次操作。

    小 Y 是一个很有经济头脑的员工,通过较长时间的运作和行情测算,他已经知道了未来 N N N 天内的 A 券和 B 券的价值以及 R a t e \mathrm{Rate} Rate。他还希望能够计算出来,如果开始时拥有 S S S 元钱,那么 N N N 天后最多能够获得多少元钱。

    输入格式

    第一行两个正整数 N , S N,S N,S,分别表示小 Y 能预知的天数以及初始时拥有的钱数。

    接下来 N N N 行,第 K K K 行三个实数 A K , B K , R a t e K A_K,B_K,\mathrm{Rate} _ K AK,BK,RateK ,意义如题目中所述。

    输出格式

    只有一个实数 M a x P r o f i t \mathrm{MaxProfit} MaxProfit,表示第 N N N 天的操作结束时能够获得的最大的金钱数目。答案保留 3 3 3 位小数。

    样例
    样例输入
    3 100
    1 1 1
    1 2 2
    2 2 3
    
    样例输出
    225.000
    
    提示
    时间用户操作人民币(元)A 券的数量B 券的数量
    开户 100 100 100 0 0 0 0 0 0
    第一天买入 100 100 100 0 0 0 50 50 50 50 50 50
    第二天卖出 100 % 100\% 100% 150 150 150 0 0 0 0 0 0
    第二天买入 150 150 150 0 0 0 75 75 75 37.5 37.5 37.5
    第三天卖出 100 % 100\% 100% 225 225 225 0 0 0 0 0 0

    本题没有部分分,你的程序的输出只有和标准答案相差不超过 0.001 0.001 0.001 时,才能获得该测试点的满分,否则不得分。

    测试数据设计使得精度误差不会超过 1 0 − 7 10^{-7} 107

    对于 40 % 40\% 40% 的测试数据,满足 N ≤ 10 N \le 10 N10

    对于 60 % 60\% 60% 的测试数据,满足 N ≤ 1000 N \le 1 000 N1000

    对于 100 % 100\% 100% 的测试数据,满足 N ≤ 1 0 5 N \le 10^5 N105

    对于 100 % 100\% 100% 的测试数据,满足:

    0 < A K ≤ 10 0 < A_K \leq 10 0<AK10 0 < B K ≤ 10 0 < B_K\le 10 0<BK10 0 < R a t e K ≤ 100 0 < \mathrm{Rate}_K \le 100 0<RateK100 M a x P r o f i t ≤ 1 0 9 \mathrm{MaxProfit} \leq 10^9 MaxProfit109

    输入文件可能很大,请采用快速的读入方式。

    必然存在一种最优的买卖方案满足:

    每次买进操作使用完所有的人民币,每次卖出操作卖出所有的金券。

    题解

    前置知识: c d q cdq cdq分治

    题目已经说了,必然存在一种最优的买卖方案满足:每次买进操作使用完所有的人民币,每次卖出操作卖出所有的金券。所以我们设 f i f_i fi表示在第 i i i天将所有金券卖出所获得的最多的钱。设第 i i i天得到的金券数是第 j j j天买入的, x j x_j xj表示第 j j j天A券的数量, y j y_j yj表示第 j j j天B券的数量,则有:

    x j = r j f j r j a j + b j , y j = f j r j a j + b j x_j=\frac{r_j f_j}{r_j a_j+b_j},y_j=\frac{f_j}{r_ja_j+b_j} xj=rjaj+bjrjfj,yj=rjaj+bjfj

    即可得到状态转移方程 f i = x j a i + y j b i f_i=x_ja_i+y_jb_i fi=xjai+yjbi,变为斜截式方程得 y j = − a i b i x j + f i b i y_j=-\frac{a_i }{b_i}x_j+\frac{f_i}{b_i} yj=biaixj+bifi

    此时,对于平面上每个点 ( x , y ) (x,y) (x,y),我们只需用斜率为 − a i b i -\frac{a_i}{b_i} biai的直线去切其中一个点,可以使截距最大的点即为最优决策点。

    至于如何求使其截距最大的点,斜率优化DP中有讲解(可看可不看,下文有讲解)

    接下来就用 c d q cdq cdq分治来解决问题。对于当前区间 [ l , r ] [l,r] [l,r],将其分为左右两个区间。对左区间按 x i x_i xi从小到大排序,对右区间按 − a i b i -\frac{a_i}{b_i} biai从小到大排序。用栈来维护左区间所形成的斜率单调递减的凸包,然后用右区间的点依次匹配左区间的点。对于每次匹配,如果左区间的点 i i i与左边的点连成的线斜率小于当前当前枚举到的斜率,则 i i i左边的点比 i i i更优。

    因为用的是栈,所以平摊下来每次是 O ( n ) O(n) O(n)的,总时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)

    code

    #include
    using namespace std;
    const double eps=1e-8,inf=1e9;
    int n,h[100005];
    double f[100005];
    struct node{
        double a,b,r,x,y,k;
        int id;
    }w[100005],w1[100005];
    bool cmp1(node ax,node bx){
        return ax.k<bx.k;
    }
    bool cmp2(node ax,node bx){
        return ax.x<bx.x;
    }
    double gt(int i,int j){
        if(fabs(w[i].x-w[j].x)<=eps) return inf;
        return (w[j].y-w[i].y)/(w[j].x-w[i].x);
    }
    void cdq(int l,int r){
        if(l==r){
            f[l]=max(f[l],f[l-1]);
            w[l].y=f[l]/(w[l].a*w[l].r+w[l].b);w[l].x=w[l].y*w[l].r;
            return;
        }
        int mid=l+r>>1;
        for(int o=l,i=l-1,j=mid;o<=r;o++){
            if(w[o].id<=mid) w1[++i]=w[o];
            else w1[++j]=w[o];
        }
        for(int o=l;o<=r;o++) w[o]=w1[o];
        cdq(l,mid);
        int tp=0;
        for(int i=l;i<=mid;i++){
            while(tp>=2&&gt(h[tp],i)+eps>gt(h[tp-1],h[tp])) --tp;
            h[++tp]=i;
        }
        for(int i=mid+1;i<=r;i++){
            while(tp>=2&&gt(h[tp-1],h[tp])<=w[i].k+eps) --tp;
            f[w[i].id]=max(f[w[i].id],w[h[tp]].x*w[i].a+w[h[tp]].y*w[i].b);
        }
        cdq(mid+1,r);
        sort(w+l,w+r+1,cmp2);
    }
    int main()
    {
        scanf("%d%lf",&n,&f[0]);
        for(int i=1;i<=n;i++){
            scanf("%lf%lf%lf",&w[i].a,&w[i].b,&w[i].r);
            w[i].k=-w[i].a/w[i].b;w[i].id=i;
        }
        sort(w+1,w+n+1,cmp1);
        cdq(1,n);
        printf("%.3f",f[n]);
        return 0;
    }
    
  • 相关阅读:
    可变电阻元件封装
    使用 HTML、CSS 和 JS 制作一个中国象棋
    设计模式概述
    JavaScript原生
    同花顺自动化交易股票下单接口API量化系统,别信那些外挂插件和软件,头部券商有现成的接口
    【数据结构与算法】之深入解析“两个有序数组的第K小乘积”的求解思路与算法示例
    Redis 缓存数据库
    PointNet++改进策略 :模块改进 | EdgeConv | DGCNN, 动态图卷积在3d任务上应用
    【启扬方案】基于启扬安卓屏一体机的医疗手推车解决方案
    牛血清白蛋白BSA/人血清白蛋白HSA/卵清白蛋白OVA纳米粒偶联CTT2肽(作用机理)
  • 原文地址:https://blog.csdn.net/tanjunming2020/article/details/127095680