【NOI2007】货币兑换
P1472 - 【NOI2007】货币兑换
Description
小 Y最近在一家金券交易所工作。该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和B纪念券(以下简称B券)。每个持有金券的顾客都有一个自己的 帐户。金券的数目可以是一个实数。每天随着市场的起伏波动,两种金券都有自己当时的价值,即每一单位金券当天可以兑换的人民币数目。我们记录第K天中A券 和B券的价值分别为AK和BK(元/单位金券)。
为了方便顾客,金券交易所提供了一种非常方便的交易方式:比例交易法。
比例交易法分为两个方面:
a)卖出金券:顾客提供一个[0,100]内的实数OP作为卖出比例,其意义为:将OP%的A券和OP%的B券以当时的价值兑换为人民币;
b)买入金券:顾客支付IP元人民币,交易所将会兑换给用户总价值为IP的金券,并且,满足提供给顾客的A券和B券的比例在第K天恰好为RateK;
例如,假定接下来3天内的Ak、Bk、RateK的变化分别为:
注意到,同一天内可以进行多次操作。
小Y是一个很有经济头脑的员工,通过较长时间的运作和行情测算,他已经知道了未来N天内的A券和B券的价值以及Rate。他还希望能够计算出来,如果开始时拥有S元钱,那么N天后最多能够获得多少元钱。
Input
第一行两个正整数N、S,分别表示小Y能预知的天数以及初始时拥有的钱数。接下来N行,第K行三个实数AK、BK、RateK,意义如题目中所述。
Output
只有一个实数MaxProfit,表示第N天的操作结束时能够获得的最大的金钱数目。答案保留3位小数。
Sample Input
3 100
1 1 1
1 2 2
2 2 3
Sample Output
225.000
Hint
样例说明:
数据范围:
测试数据设计使得精度误差不会超过10^-7。
对于40%的测试数据,满足N ≤10;
对于60%的测试数据,满足N ≤1 000;
对于100%的测试数据,满足N ≤100 000;
对于100%的测试数据,满足:
0 < Ak ≤ 10
0 < Bk≤ 10
0 < Ratek ≤ 100
MaxProfit ≤ 10^9
提示
输入文件可能很大,请采用快速的读入方式。
必然存在一种最优的买卖方案满足: 每次买进操作使用完所有的人民币;每次卖出操作卖出所有的金券。
【评分方法】
本题没有部分分,你的程序的输出只有和标准答案相差不超过0.001时,才能获得该测试点的满分,否则不得分。
题目中有一句:必然存在一种最优的买卖方案满足: 每次买进操作使用完所有的人民币;每次卖出操作卖出所有的金券。
所以可以设Y[i]表示第i天的B能买的B卷数量,则X[i]=Rate[i]。
则f[i]=A[i]*X[j]+B[i]*Y[j]
所以f[i]=max(f[i],A[i]*(Rate[j]*f[j])/(A[j]*Rate[j]+B[j]) +B[i]*f[j]/(A[j]*Rate[j]+B[j]));
然后就有60分了。
AC做法:斜率优化。
f[i]=A[i]*X[j]+B[i]*Y[j].
f[i]=A[i]*(X[j]+B[i]/A[i]*Y[j])
可以看出A[i]和B[i]都是常数。
所以就是求B[i]/A[i]*Y[j]+X[j]的最大值。kx+b,斜率优化。
但这个Y[j]并不是单调的啊,所以单调队列不能用了。
看数据范围,显然这道题的正解并不是O(n)的。
所以就有一种神奇的东西,可以nlogn搞完,叫做CDQ分治...好像这道题就是这个算法的发明题。
按照斜率优化线的做法,要在双端队列中维护斜率递减的直线,好像维护的这东西叫做半平面交。
所以就先按照时间排序,从中间二分,保证左边的时间都小于右边的时间,然后用左边更新右边。
把左边按照斜率升序排序,维护一个半平面交。
把右边按照x坐标升序排序,然后再线性扫一遍,因为这时已经满足单调性了。
还有一堆的细节要处理。
1 #include <algorithm> 2 #include <iostream> 3 #include <cstdlib> 4 #include <cstring> 5 #include <cstdio> 6 #include <cmath> 7 #define maxn 100010 8 using namespace std; 9 double A[maxn],B[maxn],Rate[maxn],f[maxn]; 10 struct data{ 11 double x,k,b; 12 int id; 13 }g[maxn],p[maxn],q[maxn]; 14 bool cmp(const data &a,const data &b){ 15 return a.x<b.x; 16 } 17 bool cmpp(const data &a,const data &b){ 18 return a.k<b.k; 19 } 20 void CDQ(int l,int r){ 21 if(l==r) return; 22 int mid=(l+r)>>1,l1=l,l2=mid+1,head=1,tail=0; 23 double MAX=0; 24 for(int i=l;i<=r;i++){ 25 if(g[i].id<=mid) p[l1++]=g[i]; 26 else p[l2++]=g[i]; 27 } 28 for(int i=l;i<=r;i++) g[i]=p[i]; 29 CDQ(l,mid); 30 sort(g+l,g+mid+1,cmpp); 31 for(int i=l;i<=mid;i++){ 32 while(head<tail && (g[i].b-q[tail].b)*(-q[tail].k+q[tail-1].k)<=(q[tail].b-q[tail-1].b)*(-g[i].k+q[tail].k)) 33 tail--; 34 q[++tail]=g[i],MAX=max(MAX,f[g[i].id]); 35 } 36 sort(g+mid+1,g+r+1,cmp); 37 for(int i=mid+1;i<=r;i++){ 38 while(head<tail&&q[head].k*g[i].x+q[head].b<=q[head+1].k*g[i].x+q[head+1].b) 39 head++; 40 f[g[i].id]=max(f[g[i].id],max(MAX,A[g[i].id]*(q[head].k*g[i].x+q[head].b))); 41 g[i].k=f[g[i].id]/(A[g[i].id]*Rate[g[i].id]+B[g[i].id]),g[i].b=g[i].k*Rate[g[i].id]; 42 } 43 CDQ(mid+1,r); 44 } 45 int main() 46 { 47 //freopen("!.in","r",stdin); 48 //freopen("!.out","w",stdout); 49 int n; 50 scanf("%d%lf",&n,&f[0]); 51 for(int i=1;i<=n;i++){ 52 scanf("%lf%lf%lf",&A[i],&B[i],&Rate[i]); 53 f[i]=f[0],g[i].id=i,g[i].x=B[i]/A[i]; 54 g[i].k=f[i]/(A[i]*Rate[i]+B[i]); 55 g[i].b=Rate[i]*f[i]/(A[i]*Rate[i]+B[i]); 56 } 57 CDQ(1,n); 58 /*for(int i=2;i<=n;i++){ 59 for(int j=1;j<i;j++) 60 f[i]=max(f[i],A[i]*(Rate[j]*f[j])/(A[j]*Rate[j]+B[j])+B[i]*f[j]/(A[j]*Rate[j]+B[j])); 61 f[i]=max(f[i],f[i-1]); 62 }*/ 63 printf("%.3lf",f[n]); 64 return 0; 65 }