bzoj1492: [NOI2007]货币兑换Cash
问题描述
小 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 的变化分别为:
时间 Ak Bk Ratek
第一天 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行三个实数Ak、Bk、Ratek,意义如题目中所述。
输出文件
只有一个实数 MaxProfit,表示第 N 天的操作结束时能够获得的最大的金钱
数目。答案保留3位小数。
输入样例
3 100
1 1 1
1 2 2
2 2 3
输出样例
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时,才能
获得该测试点的满分,否则不得分。
数据规模和约定
测试数据设计使得精度误差不会超过1e-7。
对于40%的测试数据,满足N ≤ 10;
对于60%的测试数据,满足N ≤ 1 000;
对于100%的测试数据,满足N ≤ 100 000;
对于100%的测试数据,满足:
0 < Ak ≤ 10
0 < Bk≤ 10
0 < Ratek ≤ 100
MaxProfit ≤ 1e9
提示
输入文件可能很大,请采用快速的读入方式。
必然存在一种最优的买卖方案满足:
每次买进操作使用完所有的人民币;
每次卖出操作卖出所有的金券。
思路:首先是O(n^2)DP,还是比较简单的。设第i天能买B券数量为Y[i],能买的A券数量为X[i]=Rate[i]*Y[i],最大收益为f[i].
所以:A[i]*Rate[i]*Y[i]+B[i]*Y[i]=f[i]
so:Y[i]=f[i]/(A[i]*Rate[i]+B[i])
那么,X[i]=(Rate[i]*f[i])/(A[i]*Rate[i]+B[i])
f[i]=max(f[i-1],X[j]*A[i]+Y[j]*B[i]) (j<i)
接下来就是斜率优化的过程了
f[i]=X[j]*A[i]+Y[j]*B[i]
即:Y[j]=-(A[i]/B[i])*X[j]+f[i]/B[i];
B[i]是大于0的常量,所以要使f[i]最大化,就要使该式子的截距f[i]/B[i]最大化
画图观察得知:对于任意斜率,使截距最大的点X一定在一个上凸壳上,如果在上凸壳内部,上凸壳上一定会有点比它优
所以我们只要维护一个上凸壳。
之前的斜率优化因为插入点的x单调,并且查询的斜率单调,所以可以用单调队列维护
如果x单调,但是查询的斜率不单调,那么就要在单调队列上二分。
这题很坑爹,都不单调,也就是我们有可能在中间加点,所以要用splay来维护x坐标,lk记录与左边相邻点的斜率,rk记录与右边相连的点斜率。
splay怎么维护呢?
设查询的斜率为k
当lk[x]>k&&rk[x]<[k]时,截距就能取到最大:
对于一个点我们先把它加进去,然后就有两种情况:
一种是在原凸壳内部,那么lk[x]<rk[x],这种点是没有意义的,删去即可
另一种是在原凸壳外部,那么lk[x]>rk[x],加入这个点后,为了维护上凸壳,我们要删去一些原有的点:
对于左边的点y:第一个lk[y]>=slope(y,x)(斜率)的点就是要保存的的点,右边也一样,只是反过来,然后删除中间的一段即可
最后是一些坑爹的细节:对于最左边的点x,lk[x]要赋成极大值inf,不然因为它左边没有点,查询的时候可能死循环或者出一些奇怪的问题,最右边的点同理。删除完之后也要注意这个问题...
另外CDQ分治也能做,以后再来填坑..
<span style="font-size:14px;">#include<cstdio> #include<cstring> #include<iostream> #include<cassert> #include<algorithm> using namespace std; const int maxn=100010,inf=1e9; const double eps=1e-9; inline double read(){ int a=0; long long b=0,c=1; char tmp; while(!isdigit(tmp=getchar())); do a=a*10+tmp-'0'; while(isdigit(tmp=getchar())); if(tmp!='.') return a; tmp=getchar(); do b=b*10+tmp-'0',c=c*10; while(isdigit(tmp=getchar())); return a+b/(double)c; } int n,tim,cnt;double rate[maxn],a[maxn],b[maxn],x[maxn],y[maxn],f[maxn]; double fabs(double a){return a>0?a:-a;} double slope(int j,int k){return fabs(x[j]-x[k])<eps?-inf:1.0*(y[j]-y[k])/(double)(x[j]-x[k]);} struct spy{ int root,id,c[maxn][2],fa[maxn];double lk[maxn],rk[maxn]; int which(int x){return c[fa[x]][1]==x;} void rotate(int x){ int y=fa[x],z=fa[y],nx=which(x),ny=which(y); fa[c[x][!nx]]=y,c[y][nx]=c[x][!nx]; fa[x]=z;if (z) c[z][ny]=x; fa[y]=x,c[x][!nx]=y; } void splay(int xx,int goal){ while (fa[xx]!=goal){ // printf("%d %d %d\n",root,xx,fa[xx]); if (fa[fa[xx]]==goal) rotate(xx); else if (which(xx)==which(fa[xx])) rotate(fa[xx]),rotate(xx); else rotate(xx),rotate(xx); } if (!goal) root=xx; } int getpre(int xx){//向左找 int t=c[xx][0],res=t; for (;t;){ if (slope(t,xx)<=lk[t]+eps) res=t,t=c[t][1]; else t=c[t][0]; } return res; } int getnext(int xx){//向右找 int t=c[xx][1],res=t; for (;t;){ if (slope(t,xx)>=rk[t]-eps) res=t,t=c[t][0]; else t=c[t][1]; } return res; } void insert(){ int t=root;id++; if (root==0) {root=id;return;} for (;;){ if (x[id]<=x[t]){ if (!c[t][0]){c[t][0]=id,fa[c[t][0]]=t;splay(id,0);return;} else t=c[t][0]; } else{ if (!c[t][1]){c[t][1]=id,fa[c[t][1]]=t,splay(id,0);return;} else t=c[t][1]; } } } int find(double k){//找最优 if (root==0) return 0; int t=root; // cnt++; for (;;){ // watch(); // if (tim%1000==0)printf("%d %.10f %.10f %.10f\n",t,lk[t],rk[t],k); if (rk[t]>k+eps)t=c[t][1]; else if (lk[t]+eps<k)t=c[t][0]; else if (rk[t]-eps<=k&&lk[t]>=k-eps) return t; else assert(0); } } void repair(int t){//维护凸包 splay(t,0); if (c[t][0]){//左 int le=getpre(t); splay(le,root),c[le][1]=0,fa[c[le][1]]=0; lk[t]=rk[le]=slope(le,t); } else lk[t]=inf; if (c[t][1]){//右 int ri=getnext(t); splay(ri,root),c[ri][0]=0,fa[c[ri][0]]=0; rk[t]=lk[ri]=slope(ri,t); } else rk[t]=-inf; if (lk[t]<=rk[t]+eps){//如果在凸包内,删去 splay(t,0); if (!c[t][0]){root=c[t][1],fa[c[t][1]]=0,lk[c[t][1]]=inf;} else{ int le=getpre(t),ri=getnext(t); if (!le&&!ri){fa[t]=root=0;return;} if (!le){splay(ri,0),fa[c[ri][0]]=0,c[ri][0]=0,lk[ri]=inf;return;}//边界一定要注意 if (!ri){splay(le,0),fa[c[le][1]]=0,c[le][1]=0,rk[le]=-inf;return;} fa[c[t][0]]=0,c[t][0]=0; splay(le,0),c[le][1]=c[t][1],fa[c[t][1]]=le; rk[le]=lk[ri]=slope(le,ri); } } } void watch(){ ++tim;if (tim%1000==0) printf("%d\n",tim); //printf("%d\n",++tim); } }T; int main(){ // freopen("cash.in","r",stdin);freopen("cash.out","w",stdout); scanf("%d",&n);f[0]=read(); for (int i=1;i<=n;i++) a[i]=read(),b[i]=read(),rate[i]=read(); for (int i=1;i<=n;i++){//T.watch(); int j=T.find(-a[i]/b[i]); f[i]=max(f[i-1],x[j]*a[i]+y[j]*b[i]); y[i]=f[i]/(a[i]*rate[i]+b[i]); x[i]=y[i]*rate[i];T.insert(),T.repair(i); } // for (int i=1;i<=n;i++)printf("%.3f\n",f[i]); printf("%.3f\n",f[n]); return 0; } </span>