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>

posted @ 2015-06-29 20:29  orzpps  阅读(152)  评论(0编辑  收藏  举报