zoukankan      html  css  js  c++  java
  • 【洛谷P4027】货币兑换

    题目

    题目链接:https://www.luogu.com.cn/problem/P4027

    小 Y 最近在一家金券交易所工作。该金券交易所只发行交易两种金券:A 纪念券(以下简称 A 券)和 B 纪念券(以下简称 B 券)。每个持有金券的顾客都有一个自己的帐户。金券的数目可以是一个实数。
    每天随着市场的起伏波动,两种金券都有自己当时的价值,即每一单位金券当天可以兑换的人民币数目。我们记录第 K 天中 A 券和 B 券的价值分别为 \(A_K\)\(B_K\) (元/单位金券)。
    为了方便顾客,金券交易所提供了一种非常方便的交易方式:比例交易法。
    比例交易法分为两个方面:
    a) 卖出金券:顾客提供一个[0,100]内的实数 OP 作为卖出比例,其意义为:将 OP%的 A 券和 OP%的 B 券以当时的价值兑换为人民币;
    b) 买入金券:顾客支付 IP 元人民币,交易所将会兑换给用户总价值为IP 的金券,并且,满足提供给顾客的 A 券和 B 券的比例在第 K 天恰好为 \(Rate_K\)
    例如,假定接下来 3 天内的 \(A_k\)\(B_k\)\(Rate_K\) 的变化分别为:

    时间 \(A_k\) \(B_k\) \(Rate_k\)
    第一天 1 1 1
    第二天 1 2 2
    第三天 2 2 3

    假定在第一天时,用户手中有 100 元人民币但是没有任何金券。
    用户可以执行以下的操作:

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

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

    思路

    很容易证明每次操作必然全部买入或卖出。这个结论十分好证,因为买入或卖出的比例是固定的。
    所以一定是不断全部买入,全部卖出的过程重复。设 \(f[i]\) 表示在第 \(i\) 天全部卖出的最大收益,那么考虑枚举 \(j\in[1,i)\),表示最近一次买入是在第 \(j\) 天。已知总买入的总花费为 \(f[j]\),设买入了 \(x\) 单位 B 券,那么可以列出方程

    \[x·b[j]+x·r[j]·a[j]=f[j] \]

    解得 \(x=\frac{f[j]}{b[j]+r[j]·a[j]}\)。令 \(t[j]=x=\frac{f[j]}{b[j]+r[j]·a[j]}\),那么买入 A 券 \(t[j]·r[i]\) 单位,买入 B 券 \(t[j]\) 单位。
    所以有

    \[f[i]=max(f[i-1],t[j]·b[i]+t[j]·r[j]·a[i]) \]

    先不考虑 \(f[i-1]\),得到 \(f[i]=t[j]·b[i]+t[j]·r[j]·a[i]\)。我们发现这个多项式中包含 \(i,j\) 两项的并不是一个单项式而是多项式,所以不可以直接优化。
    但是我们将等号两边同时除以 \(a[i]\) 可以得到 \(\frac{f[i]}{a[i]}=\frac{b[i]}{a[i]}·t[j]+t[j]·r[j]\)。移项得 \(t[j]·r[j]=-\frac{b[i]}{a[i]}·t[j]+\frac{f[i]}{a[i]}\),而这个式子就是一个标准的 "\(y=kx+b\)"。
    考虑使用斜率优化。但是一般的斜率优化因为要按点横坐标顺序加点维护凸壳,但是此题每一个点的坐标为 \((t[j],t[j]·r[j])\),其中横坐标 \(t[j]\) 是不满足单调性的。一种直观的维护方式是平衡树维护凸壳。但是有一种相对简单的方式为 cdq 分治维护凸壳。
    按照时间进行分治,对于一个区间 \([l,r]\),先将其左子区间维护好,然后计算左子区间对右子区间的贡献。将左子区间按照点的横坐标升序排序,右子区间按照斜率 \(-\frac{b[i]}{a[i]}\) 升序排序。然后指针扫描计算贡献即可。
    计算完后再将右子区间进行分治。注意当 \(l=r\) 时,\(f[l]\) 要与 \(f[l-1]\)\(\max\)
    时间复杂度 \(O(n\log^2 n)\),似乎采用归并排序可以降至 \(O(n\log n)\)

    代码

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    const int N=100010;
    const double eps=1e-7;
    int n,top,st[N];
    double f[N];
    
    struct node
    {
    	double a,b,r,t,k;
    	int id;
    }a[N];
    
    bool cmp1(node x,node y)
    {
    	return x.t<y.t;
    }
    
    bool cmp2(node x,node y)
    {
    	return x.k<y.k;
    }
    
    bool cmp3(node x,node y)
    {
    	return x.id<y.id;
    }
    
    double slope(int x,int y)
    {
    	if (fabs(a[x].t-a[y].t)<=eps) return 1000000000.0;
    	return (a[x].t*a[x].r-a[y].t*a[y].r)/(a[x].t-a[y].t);
    }
    
    void cdq(int l,int r)
    {
    	if (l==r)
    	{
    		f[l]=max(f[l],f[l-1]);
    		a[l].t=f[l]/(a[l].b+a[l].r*a[l].a);
    		return;
    	}
    	int mid=(l+r)>>1;
    	sort(a+l,a+r+1,cmp3);
    	cdq(l,mid);
    	sort(a+l,a+mid+1,cmp1);
    	sort(a+mid+1,a+r+1,cmp2);
    	st[1]=l; top=1;
    	for (int i=l+1;i<=mid;i++)
    	{
    		while (top>1 && slope(st[top-1],st[top])<slope(st[top],i)) top--;
    		st[++top]=i;
    	}
    	for (int i=mid+1;i<=r;i++)
    	{
    		while (top>1 && slope(st[top-1],st[top])<a[i].k) top--;
    		int j=st[top];
    		f[a[i].id]=max(f[a[i].id],a[j].t*a[i].b+a[j].t*a[j].r*a[i].a);
    	}
    	cdq(mid+1,r);
    }
    
    int main()
    {
    	cin>>n>>f[0];
    	for (int i=1;i<=n;i++)
    	{
    		scanf("%lf%lf%lf",&a[i].a,&a[i].b,&a[i].r);
    		a[i].id=i; a[i].k=-a[i].b/a[i].a;
    	}
    	cdq(1,n);
    	printf("%.8lf",f[n]);
    	return 0;
    }
    
  • 相关阅读:
    IEXPRESS
    .NET protector
    aspnet_compiler
    Ubuntu 添加新用户并制定目录和shell,并配置为root组用户|sudoer 修改
    键盘驱动的原理
    几个常用的宏:likely和unlikely __raw_writel
    android 静音与振动
    Ubuntu 添加新用户并制定目录和shell,并配置为root组用户|sudoer 修改
    Linux输入子系统
    Android input device request_irq() 的 注册中断服务
  • 原文地址:https://www.cnblogs.com/stoorz/p/13415103.html
Copyright © 2011-2022 走看看