小 Y 最近在一家金券交易所工作。该金券交易所只发行交易两种金券:A 纪念券(以下简称 A 券)和 B 纪念券(以下简称 B 券)。每个持有金券的顾客都有一个自己的帐户。金券的数目可以是一个实数。
每天随着市场的起伏波动,两种金券都有自己当时的价值,即每一单位金券当天可以兑换的人民币数目。我们记录第 K 天中 A 券和 B 券的价值分别为 AKA_KAK 和BKB_KBK (元/单位金券)。
为了方便顾客,金券交易所提供了一种非常方便的交易方式:比例交易法。
比例交易法分为两个方面:
a) 卖出金券:顾客提供一个[0,100]内的实数 OP 作为卖出比例,其意义为:将 OP%的 A 券和 OP%的 B 券以当时的价值兑换为人民币;
b) 买入金券:顾客支付 IP 元人民币,交易所将会兑换给用户总价值为IP 的金券,并且,满足提供给顾客的 A 券和 B 券的比例在第 K 天恰好为 RateKRate_KRateK ;
例如,假定接下来 3 天内的 AkA_kAk 、BkB_kBk 、RateKRate_KRateK 的变化分别为
时间 AkA_kAk BkB_kBk RatekRate_kRatek
第一天 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 天后最多能够获得多少元钱。
输入格式:
第一行两个正整数 N、S,分别表示小 Y 能预知的天数以及初始时拥有的钱数。
接下来 N 行,第 K 行三个实数 AKA_KAK 、BKB_KBK 、RateKRate_KRateK ,意义如题目中所述。
输出格式:
只有一个实数 MaxProfit,表示第 N 天的操作结束时能够获得的最大的金钱数目。答案保留 3 位小数。
输入样例#1:
3 100
1 1 1
1 2 2
2 2 3
输出样例#1:
225.000
时间 用户操作 人民币(元) A 券的数量 B 券的数量
开户 无 100 0 0
第一天 买入 100 元 0 50 50
第二天 卖出 100% 150 0 0
第二天 买入 150 元 0 75 37.5
第三天 卖出 100% 225 0 0
本题没有部分分,你的程序的输出只有和标准答案相差不超过0.001 时,才能获得该测试点的满分,否则不得分。
测试数据设计使得精度误差不会超过 10−7 10 − 7 。
对于 40%的测试数据,满足 N ≤ 10;
对于 60%的测试数据,满足 N ≤ 1 000;
对于 100%的测试数据,满足 N ≤ 100 000;
对于 100%的测试数据,满足:
0 < AK A K ≤ 10;
0 < BK B K ≤ 10;
0 < RateK R a t e K ≤ 100
MaxProfit M a x P r o f i t ≤ 10910^9109 ;
输入文件可能很大,请采用快速的读入方式。
必然存在一种最优的买卖方案满足:
每次买进操作使用完所有的人民币;
每次卖出操作卖出所有的金券。
认真做的话应该是平衡树维护凸包,但是我完全搞不懂(一会可能还要去看看)
于是就按照CDQ论文来做了一下
不用考虑哪一天的钱买哪一天的货币,只要考虑兑换之后的货币在哪一天换掉即可(这是句废话,逻辑混乱并且完全不需要读懂)
考虑第 i i 天卖出货币(按照买股票的说法来讲)
那么,值得一提的就是,获得货币的时间只能是 [1,i−1] [ 1 , i − 1 ] 之间
假设第 i i 天在手里的货币量为 X[i],Y[i] X [ i ] , Y [ i ] ,最大收益为 f[i] f [ i ]
货币兑换为 CA[i],CB[i] C A [ i ] , C B [ i ]
那么第 i i 天兑换货币的量为
CA[i]=f[i]/(Ai∗Rate[i]+Bi)∗Rate[i] C A [ i ] = f [ i ] / ( A i ∗ R a t e [ i ] + B i ) ∗ R a t e [ i ]
CB[i]=f[i]/(Ai∗Rate[i]+Bi) C B [ i ] = f [ i ] / ( A i ∗ R a t e [ i ] + B i )
那么在第 i i 天把货币都换成钱就有点麻烦了(货币好像就是钱??)
那么对于 j,k∈[1,i−1] j , k ∈ [ 1 , i − 1 ] , j j 优于 k k 就有
因为 j,k j , k 无序(我们没有规定它有序)
因此不妨设 CA[j]>CA[k] C A [ j ] > C A [ k ]
那么根据这个,我们就可以将每一个点的 CB C B 作为 x x , CA C A 作为 y y
然后按照 x,y x , y 升序排序,然后维护一个凸包
于是乎,问题解决了一半
另一半的问题是
换句话说 当有一个新的点 i i 被处理完,并放进来的时候,我们并不知道 i i 的插入位置
正规做法用Splay解决了这个问题,但是我们选择用更加简单粗暴的方法:CDQ分治
仔细回想一下 CDQ C D Q 的处理过程你就会发现,它始终是用前面的更新后面的
所以 解法如下
当前处理区间 [l,r] [ l , r ]
①先处理 [l,mid] [ l , m i d ]
②然后将 [l,mid] [ l , m i d ] 内的点按照 x,y, x , y , 升序排列,维护一个凸包
③然后用这个凸包更新 [mid+1,r] [ m i d + 1 , r ] 内的点的 f f ,并且要求这些点的 k k 按照降序排序(免去二分查找的复杂度,可以扫一遍直接过)
④再处理 [mid+1,r] [ m i d + 1 , r ]
⑤重新按照原本的顺序排序
注意一个细节
#include
#include
#include
using namespace std;
const double eps=1e-9;
const double inf=1e9;
inline double fabs(double x){return x>0?x:-x;}
struct node
{
double q,a,b,r,k;
int i;
}A[100123],B[100123];
bool cmp(node a,node b){return a.kstruct pot{double x,y;}p[100123],np[100123];
bool operator < (pot a,pot b){return (a.xfabs(a.x-b.x)<=eps)&&(a.ydouble f[100123];
int st[100123];
double slop(int a,int b)
{
if(fabs(p[a].x-p[b].x)<=eps)return -inf;
return (p[b].y-p[a].y)/(p[b].x-p[a].x);
}
void solve(int l,int r)
{
if(l==r)
{
f[l]=max(f[l],f[l-1]);//最优决策可能是继承昨天的
p[l].y=f[l]/(A[l].a*A[l].r+A[l].b);
p[l].x=p[l].y*A[l].r;
return;
}
int mid=l+r>>1,l1=l,l2=mid+1;
for(int i=l;i<=r;i++)
if(A[i].i<=mid)B[l1++]=A[i];
else B[l2++]=A[i];//按照正常顺序分左右区间,但此时左右区间的点仍然分别按照k的降序排序(分别)
for(int i=l;i<=r;i++)A[i]=B[i];
solve(l,mid);//处理左区间
int top=0;
for(int i=l;i<=mid;i++)
{
while(top>1&&slop(i,st[top])+eps>slop(st[top],st[top-1]))top--;
st[++top]=i;
}//维护凸包
int j=1;
for(int i=r;i>mid;i--)
{
while(j1])+eps)j++;//因为k按照降序排序,所以可以直接枚举斜率从小到大
f[A[i].i]=max(f[A[i].i],p[st[j]].x*A[i].a+p[st[j]].y*A[i].b);//更新答案
}
solve(mid+1,r);//处理右区间
l1=l;l2=mid+1;
//归并排序,按照斜率的点进行排序
for(int i=l;i<=r;i++)
if((p[l1]r)&&l1<=mid)np[i]=p[l1++];
else np[i]=p[l2++];
for(int i=l;i<=r;i++)p[i]=np[i];
}
int main()
{
freopen("In.txt","r",stdin);
int n;
scanf("%d%lf",&n,&f[0]);
for(int i=1;i<=n;i++)
{
A[i].i=i;
scanf("%lf%lf%lf",&A[i].a,&A[i].b,&A[i].r);
A[i].k=-A[i].a/A[i].b;
}
sort(A+1,A+n+1,cmp);//按照k的降序排序
solve(1,n);
printf("%.3lf\n",f[n]);
return 0;
}