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<bits/stdc++.h>
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;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
P2375 [NOI2014] 动物园是一道经典的动态规划题目,以下是该题的详细题意和解题思路。 【题意描述】 有两个长度为 $n$ 的整数序列 $a$ 和 $b$,你需要从这两个序列中各选出一些数,使得这些数构成一个新的序列 $c$。其中,$c$ 序列中的元素必须在原序列中严格递增。每个元素都有一个价值,你的任务是选出的元素的总价值最大。 【解题思路】 这是一道经典的动态规划题目,可以采用记忆化搜索的方法解决,也可以采用递推的方法解决。 记忆化搜索的代码如下: ```c++ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MAXN = 1005; int dp[MAXN][MAXN], a[MAXN], b[MAXN], n; int dfs(int x, int y) { if (dp[x][y] != -1) return dp[x][y]; if (x == n || y == n) return 0; int res = max(dfs(x + 1, y), dfs(x + 1, y + 1)); if (a[x] > b[y]) { res = max(res, dfs(x, y + 1) + b[y]); } return dp[x][y] = res; } int main() { scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", &a[i]); for (int i = 0; i < n; i++) scanf("%d", &b[i]); memset(dp, -1, sizeof(dp)); printf("%d\n", dfs(0, 0)); return 0; } ``` 其中,dp[i][j]表示选到a数组中第i个元素和b数组中第j个元素时的最大价值,-1表示未计算过。dfs(x,y)表示选到a数组中第x个元素和b数组中第y个元素时的最大价值,如果dp[x][y]已经计算过,则直接返回dp[x][y]的值。如果x==n或者y==n,表示已经遍历完一个数组,直接返回0。然后就是状态转移方程了,如果a[x] > b[y],则可以尝试选b[y],递归调用dfs(x, y+1)计算以后的最大价值。否则,只能继续遍历数组a,递归调用dfs(x+1, y)计算最大价值。最后,返回dp[0][0]的值即可。 递推的代码如下: ```c++ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int MAXN = 1005; int dp[MAXN][MAXN], a[MAXN], b[MAXN], n; int main() { scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", &a[i]); for (int i = 0; i < n; i++) scanf("%d", &b[i]); for (int i = n - 1; i >= 0; i--) { for (int j = n - 1; j >= 0; j--) { dp[i][j] = max(dp[i + 1][j], dp[i + 1][j + 1]); if (a[i] > b[j]) { dp[i][j] = max(dp[i][j], dp[i][j + 1] + b[j]); } } } printf("%d\n", dp[0][0]); return 0; } ``` 其中,dp[i][j]表示选到a数组中第i个元素和b数组中第j个元素时的最大价值。从后往前遍历数组a和数组b,依次计算dp[i][j]的值。状态转移方程和记忆化搜索的方法是一样的。 【参考链接】 P2375 [NOI2014] 动物园:https://www.luogu.com.cn/problem/P2375
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值