【bzoj 1492】【codevs 1797】 [NOI2007]货币兑换Cash (dp+cdq分治)
1492: [NOI2007]货币兑换Cash
Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 3803 Solved: 1604
[Submit][Status][Discuss]
Description
![](http://www.lydsy.com/JudgeOnline/upload/201604/dd(1).png)
![](http://www.lydsy.com/JudgeOnline/upload/201604/dd(2).png)
Input
Output
只有一个实数MaxProfit,表示第N天的操作结束时能够获得的最大的金钱数目。答案保留3位小数。
Sample Input
1 1 1
1 2 2
2 2 3
Sample Output
HINT
Source
【题解】【dp+cdq分治】
【这道题,先贪心的考虑,如果要最大,那么每一天一定只有一种操作,我们用f[i]表示第i天将所有的钱全部兑换成A, B券,最多可以得到多少A券】
【首先,它是一道dp。方程:f[i]=max{f[j]/(a[j]*rate[j]+b[j])*rate[j]*a[i]+f[j]/(a[j]*rate[j]+b[j])*b[i]} O(n^2)】
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
double A[100010],B[100010],rate[100010];
double f[100010],ans;
int n,s;
int main()
{
int i,j;
scanf("%d%d",&n,&s);
for(i=1;i<=n;++i) scanf("%lf%lf%lf",&A[i],&B[i],&rate[i]);
ans=s; f[1]=s*rate[1]/(A[1]*rate[1]+B[1]);
for(i=2;i<=n;++i)
{
for(j=1;j<i;++j)
ans=max(ans,f[j]*A[i]+f[j]/rate[j]*B[i]);
f[i]=ans*rate[i]/(A[i]*rate[i]+B[i]);
}
printf("%.3lf\n",ans);
return 0;
}
【我们再来研究下方程,首先用x,y分别代表A券和B券卖出的数量,发现它就是f[i]=x*A[i]+y*B[i]。然后又因为x/y=rate[i] —>y=x/rate[i],所以,方程变成了:f[i]=x*A[i]+x/rate[i]*B[i]—>f[i]*rate[i]=x*rate[i]*A[i]+x*B[i],所以第i天卖出的A券的数量x[i]=f[i]*rate[i]/(rate[i]*A[i]+B[i]),第i天卖出的B券的数量y[i]=f[i]/(rate[i]*A[i]+B[i]),so,回归到原方程,即f[i]=x[i]*A[i]+y[i]*B[i]—>y[i]=f[i]/B[i]-(A[i]/B[i])*x[i],这时我们惊奇地会发现,若想要f[i]最大,那么就是求一条过(x[i],y[i])且斜率为-A[i]/B[i]的直线的最大截距。好了,现在它是一道斜率优化dp...】
【我们可以发现,(x[i],y[i])在凸壳上,使用上凸壳来维护,怎么维护?splay?!(我仿佛看见了二三百行的代码。。。)这里使用一种神奇的方法cdq分治:首先,把斜率按从大到小排序,由于l-mid区间的斜率会对mid+1-r区间的斜率产生影响,所以,在处理每个区间时,把当前区间中num<=mid的操作分出来作为左区间,然后递归,左区间计算完后可以用来更新右区间,更新右区间时,要按斜率递减的方式更新,这样一边就可以处理好全部的,然后再递归。】
【最后,将两个区间的最优值利用归并排序重新排序,返回上一层】
#include<cmath>>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define eps 1e-9
using namespace std;
struct slove{
double a,b,k,rate;
double x,y;
int num;
}d[100010],p[100010];
double f[100010];//f[i]表示第i天将所有的钱全部兑换成A, B券,最多可以得到多少A券
int n,que[100010],t;
int tmp(slove a,slove b)
{
return a.k>b.k;
}
inline double check(int i,int j)
{
if(!j) return -1e20;
if(fabs(d[i].x-d[j].x)<eps) return 1e20;
return (d[j].y-d[i].y)/(d[j].x-d[i].x);
}
inline void cdq(int l,int r)
{
if(l==r)
{
if(f[l]<f[l-1]) f[l]=f[l-1];
d[l].y=f[l]/(d[l].a*d[l].rate+d[l].b);
d[l].x=d[l].y*d[l].rate;
return;
}
int mid=(l+r)>>1,pl=l,pr=mid+1;
for(int i=l;i<=r;++i)
if(d[i].num<=mid) p[pl++]=d[i];
else p[pr++]=d[i];
for(int i=l;i<=r;++i) d[i]=p[i];//把当前区间更新为按读入顺序为关键字排序
cdq(l,mid);
t=0;
for(int i=l;i<=mid;++i)
{
while(t>=2&&check(que[t-1],que[t])<check(que[t-1],i)+eps) t--;
que[++t]=i;
}
que[++t]=0;
//左区间维护凸包
int j=1;
for(int i=mid+1;i<=r;++i)
{
while(j<t&&check(que[j],que[j+1])+eps>d[i].k) ++j;
f[d[i].num]=max(f[d[i].num],d[que[j]].x*d[i].a+d[que[j]].y*d[i].b);
}
//用左区间的点作为决策更新右区间
cdq(mid+1,r);
pl=l; pr=mid+1;
for(int i=l;i<=r;++i)
if(((d[pl].x<d[pr].x||fabs(d[pl].x-d[pr].x)<eps&&d[pl].y<d[pr].y)||pr>r)&&pl<=mid) p[i]=d[pl++];
else p[i]=d[pr++];
for(int i=l;i<=r;++i) d[i]=p[i];//把当前区间更新为以a卷数量为关键字排序
}
int main()
{
int i;
scanf("%d%lf",&n,&f[0]);
for(i=1;i<=n;++i)
{
scanf("%lf%lf%lf",&d[i].a,&d[i].b,&d[i].rate);
d[i].k=-d[i].a/d[i].b; d[i].num=i;
}
sort(d+1,d+n+1,tmp);
cdq(1,n);
printf("%.3lf\n",f[n]);
return 0;
}