bzoj 1492: [NOI2007]货币兑换Cash

Description

Input

第一行两个正整数N、S,分别表示小Y 能预知的天数以及初始时拥有的钱数。 接下来N 行,第K 行三个实数AK、BK、RateK,意义如题目中所述

Output

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

Sample Input

3 100
1 1 1
1 2 2
2 2 3

Sample Output

225.000

HINT



测试数据设计使得精度误差不会超过10-7。
对于40%的测试数据,满足N ≤ 10;
对于60%的测试数据,满足N ≤ 1 000;
对于100%的测试数据,满足N ≤ 100 000;


动态维护凸包+斜率优化

CDQ分治+斜率优化

刚好对于前面正在做的CDQ分治和斜率优化用这题和NOI2014购票来做个总结好了

首先明确一点。要使获利最大。要么全买要么全卖

那么就可以得出状态转移方程了

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]}

其中,x[j]=f[j]/(a[j]*rate[j]+b[j])*rate[j]表示第j天最多可以拥有的A货币的数量

   y[j]=f[j]/(a[j]*rate[j]+b[j])表示第j天最多可以拥有的B货币的数量

然后可以得出斜率方程 y[i]=(-a[i]/b[i])*x[i]+f[i]/b[i]

可我们发现这个斜率没有单调性。怎么办?

方法一:用平衡树动态维护凸包,然后更新的时候二分。

方法二:CDQ分治

对于区间(l,r),(l,mid)按照x排序,(mid+1,r)按照斜率排序

然后对于前面的点我们维护一个凸包。后面的点顺着更新就可以了

*关于分治内部要归并排序。如果图省事用快排的话会T掉两个点。而n^2的暴力转移也可以拿60分

【程序最后附测试数据一组】

#include<cstdio>
#include<algorithm>
using namespace std;
double eps=1e-9;
struct cash
{
     double a,b,rate;
     double x,y;
     double k;
     double f;
     int p;
}a[100001],qx[100001];
int q[100001];
double f[100001];
inline double absx(double x)
{
     if(x<0)
          x=-x;
     return x;
}
inline double getk(int j,int k)
{
	 if(absx(a[k].x-a[j].x)<=eps)
	      return -2100000000;
     return (a[k].y-a[j].y)/(a[k].x-a[j].x);
}
inline bool cmp1(cash x,cash y)
{
     if(x.x<y.x||x.x==y.y&&x.x>y.y)
          return true;
     return false;
}
inline bool cmp2(cash x,cash y)
{
     if(x.k>y.k)
          return true;
     return false;
}
inline bool cmp3(cash x,cash y)
{
     if(x.p<y.p)
          return true;
     return false;
}
inline void solve(int l,int r)
{
     int mid=(l+r)/2;
     if(l!=r)
     {
          int p1=l-1,p2=mid;
          int i,p=0;
          for(i=l;i<=r;i++)
          {
               if(a[i].p<=mid)
               {
                    p1++;
                    qx[p1]=a[i];
               }
               else
               {
                    p2++;
                    qx[p2]=a[i];
               }
          }
          for(i=l;i<=r;i++)
               a[i]=qx[i];
          solve(l,mid);
          for(i=l;i<=mid;i++)
          {
               while(p>1&&getk(q[p-1],q[p])<getk(q[p],i))
                    p--;
               p++;
               q[p]=i;
          }
          int d=1;
          for(i=mid+1;i<=r;i++)
          {
               while(d<p&&a[i].k<getk(q[d],q[d+1]))
                    d++;
               int j=q[d];
               f[a[i].p]=max(f[a[i].p],a[j].x*a[i].a+a[j].y*a[i].b);
               a[i].y=f[a[i].p]/(a[i].a*a[i].rate+a[i].b);
               a[i].x=a[i].y*a[i].rate;
          }
          solve(mid+1,r);
          p=l-1;
          int l1=l,l2=mid+1;
          while(l1<=mid&&l2<=r)
          {
               if(a[l1].x<a[l2].x||a[l1].x==a[l2].x&&a[l1].y>a[l2].y)
               {
                    p++;
                    qx[p]=a[l1];
                    l1++;
               }
               else
               {
                    p++;
                    qx[p]=a[l2];
                    l2++;
               }
          }
          if(l1<=mid)
          {
               for(i=l1;i<=mid;i++)
               {
               	    p++;
                    qx[p]=a[i];
               }
          }
          else
          {
               for(i=l2;i<=r;i++)
               {
               	    p++;
                    qx[p]=a[i];
               }
          }
          for(i=l;i<=r;i++)
               a[i]=qx[i];
     }
     else
     {
          f[l]=max(f[l-1],f[l]);
          a[l].y=f[l]/(a[l].a*a[l].rate+a[l].b);
          a[l].x=a[l].y*a[l].rate;
     }
}
int main()
{
     int n;
     double s;
     scanf("%d%lf",&n,&s);
     f[0]=s;
     int i;
     for(i=1;i<=n;i++)
     {
          scanf("%lf%lf%lf",&a[i].a,&a[i].b,&a[i].rate);
          a[i].k=-a[i].a/a[i].b;
          a[i].p=i;
     }
     sort(a+1,a+1+n,cmp2);
     solve(1,n);
    // for(i=1;i<=n;i++)
     printf("%.3lf\n",f[n]);
     return 0;
}
/*
10 100 
5.226 5.381 1.73 
5.273 5.899 1.35 
5.275 5.236 1.93 
5.769 5.863 1.78 
5.888 5.064 1.91 
5.464 5.894 1.95 
5.565 5.731 1.97 
5.568 5.305 1.31 
5.639 5.501 1.85 
5.751 5.925 1.14 
*/




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值