1492: [NOI2007]货币兑换Cash
Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 3801 Solved: 1602
[ Submit][ Status][ Discuss]
Description
Input
Output
只有一个实数MaxProfit,表示第N天的操作结束时能够获得的最大的金钱数目。答案保留3位小数。
Sample Input
1 1 1
1 2 2
2 2 3
Sample Output
HINT
Source
题解: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]);
}