bzoj 1492: [NOI2007]货币兑换Cash (dp+cdq分治)

1492: [NOI2007]货币兑换Cash

Time Limit: 5 Sec   Memory Limit: 64 MB
Submit: 3801   Solved: 1602
[ Submit][ Status][ Discuss]

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 的变化分别为:
假定在第一天时,用户手中有 100元 人民币但是没有任何金券。用户可以执行以下的操作:
注意到,同一天内可以进行多次操作。小Y是一个很有经济头脑的员工,通过较长时间的运作和行情测算,他已经
知道了未来N天内的A券和B券的价值以及Rate。他还希望能够计算出来,如果开始时拥有S元钱,那么N天后最多能
够获得多少元钱。

Input

输入第一行两个正整数N、S,分别表示小Y能预知的天数以及初始时拥有的钱数。接下来N行,第K行三个实数AK、B
K、RateK,意义如题目中所述。对于100%的测试数据,满足:0<AK≤10;0<BK≤10;0<RateK≤100;MaxProfit≤1
0^9。
【提示】
1.输入文件可能很大,请采用快速的读入方式。
2.必然存在一种最优的买卖方案满足:
每次买进操作使用完所有的人民币;
每次卖出操作卖出所有的金券。

Output

只有一个实数MaxProfit,表示第N天的操作结束时能够获得的最大的金钱数目。答案保留3位小数。

Sample Input

3 100
1 1 1
1 2 2
2 2 3

Sample Output

225.000

HINT

Source

[ Submit][ Status][ Discuss]

题解:dp+cdq分治

每一天我们有两种操作买入和卖出,我们贪心的想,要是最终的获利最大,一定不存在一天既买入又卖出,所以每天一定是单一的操作。

那么我们用f[i]表示将券全部卖出每天的最大收益,则a*A[i]+b*B[i]=f[i] (其中a,b表示A,B券的数量),又因为a/b=rate[i],所以将式子带入就得到a*A[i]+a/rate[i]*B[i]=f[i],化简得a*A[i]*rate[i]+a*B[i]=f[i]*rate[i],所以第i天a券的数目为x[i]=f[i]*rate[i]/(rate[i]*A[i]+B[i]),b券的数目为y[i]=f[i]/(rate[i]*A[i]+B[i])。

那么我们就就可以得到dp方程f[i]=max{max{x[j]*A[i]+y[j]*B[i]}(即将第j天所能得到的券全部卖出),f[i-1](当天什么都不干)}

观察式子我们可以发现我们每次需要一个最优决策点(x[j],y[j])来更新答案。

将式子变形   f[i]=x[j]*A[i]+y[j]*B[i]   =>   y[j]=f[i]/b[i]-x[j]*(a[i]/b[i])  我们发现b[i]和a[i]/b[i]是已知的,所以我们想要f[i]最大,就是求一条经过点(x[j],y[j])且斜率为-a[i]/b[i]的直线的最大截距。

我们可以发现点(x[j],y[j])一定在凸壳上,维护动态凸壳,splay?太麻烦啦,我们要利用一个很神奇的思路——cdq分治。

我们把斜率按照从小到大的顺序排序,然后二分一个中点,因为1...i的点会对i+1...n的点产生影响,但是i+1...n的点不会对1...i产生影响。于是我们每次把pos<=mid的点分到一起,然后递归。左边区间的点计算完答案后就可以用来更新右边的区间,然后我们对于左边的点求一个凸壳因为我们刚开始就排序了,所以在分离的时候也能保证有序,于是就可以得到一个斜率递减的凸壳,然后让右边区间直线的斜率也用递减的顺序去匹配凸壳上的点,这样只用一遍就可以更新答案。然后这时候就可以递归处理右边的区间。这样递归地更新的话,我们一定可以保证在递归到i点的时候,1..i-1的点都已经更新过i点的f值了,所以保证了正确性。等左右区间都更新完成时,我们利用归并排序将两个区间得到的最优决策点排序,然后返回上一个过程继续更新答案。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define N 100003
#define eps 1e-9
#define inf 1000000000
using namespace std;
int n,m,st[N];
double f[N];
struct data
{
   double a,b,rate;
   double k;
   int pos;	
}a[N],np[N];
struct point
{
	double x,y;
	bool operator <(const point &b)const
	{
		return (x<b.x+eps||fabs(x-b.x)<=eps&&y<b.y+eps);
	}
}p[N],np1[N];
int cmp(data a,data b)
{
	return a.k<b.k;
}
double getk(int i,int j)
{
	//if (i==0)  return -inf;
	//if (j==0)  return inf;
	if (fabs(p[i].x-p[j].x)<=eps)  return -inf;
	return (p[i].y-p[j].y)/(p[i].x-p[j].x);
}
void divide(int l,int r)
{
	if (l==r)
	{
		f[l]=max(f[l-1],f[l]);
		p[l].x=(f[l]*a[l].rate)/(a[l].rate*a[l].a+a[l].b);
		p[l].y=f[l]/(a[l].rate*a[l].a+a[l].b);
		return;
	}
	int mid=(l+r)/2;
	int l1=l,l2=mid+1;
	for (int i=l;i<=r;i++)
	 if (a[i].pos<=mid)  np[l1++]=a[i];
	 else np[l2++]=a[i];
	for (int i=l;i<=r;i++) a[i]=np[i];
	divide(l,mid);
	int top=0;
	for (int i=l;i<=mid;i++)
	 {
	 	while (top>=2&&getk(i,st[top])+eps>getk(st[top],st[top-1]))  top--;
	 	st[++top]=i;
	 }
	int j=1;
	for (int i=r;i>=mid+1;i--)
	 {
	 	while (j<top&&getk(st[j],st[j+1])+eps>a[i].k) j++;
	 	int k=st[j];
	 	f[a[i].pos]=max(f[a[i].pos],p[k].x*a[i].a+p[k].y*a[i].b);
	 }
	divide(mid+1,r);
	l1=l; l2=mid+1;
	for (int i=l;i<=r;i++)
	 if((p[l1]<p[l2]||l2>r)&&l1<=mid)  np1[i]=p[l1++];
	 else np1[i]=p[l2++];
	for (int i=l;i<=r;i++)
	  p[i]=np1[i];
}
int main()
{
	scanf("%d%lf",&n,&f[0]);
	for (int i=1;i<=n;i++)
	 {
	 	scanf("%lf%lf%lf",&a[i].a,&a[i].b,&a[i].rate);
	 	a[i].pos=i;
	 	a[i].k=-a[i].a/a[i].b;
	 }
	sort(a+1,a+n+1,cmp);
	divide(1,n);
	printf("%0.3lf\n",f[n]);
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值