题目描述
小 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} 10−7 。
对于 40 % 40\% 40% 的测试数据,满足 N ≤ 10 N \le 10 N≤10。
对于 60 % 60\% 60% 的测试数据,满足 N ≤ 1000 N \le 1 000 N≤1000。
对于 100 % 100\% 100% 的测试数据,满足 N ≤ 1 0 5 N \le 10^5 N≤105。
对于 100 % 100\% 100% 的测试数据,满足:
0 < A K ≤ 10 0 < A_K \leq 10 0<AK≤10, 0 < B K ≤ 10 0 < B_K\le 10 0<BK≤10, 0 < R a t e K ≤ 100 0 < \mathrm{Rate}_K \le 100 0<RateK≤100, M a x P r o f i t ≤ 1 0 9 \mathrm{MaxProfit} \leq 10^9 MaxProfit≤109。
输入文件可能很大,请采用快速的读入方式。
必然存在一种最优的买卖方案满足:
每次买进操作使用完所有的人民币,每次卖出操作卖出所有的金券。
题解
前置知识: 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&>(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&>(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;
}