Description
Solution
首先,最优情况一定是某一天把所有金券卖出或买入是最优的。
在金券一定的情况下,分散卖一定没有统一在最优的那天卖更优。
然后,我们假定在某一天卖,则在该天前面一定会有一天的全部买入能够使价值最大。
定义ans[i]为第i天能拥有的最大钱数。
则第i天能够有的A金券数为x[i]=ans[i]/(a[i]*rate[i]+b[i])*rate[i],y[i]=ans[i]/(a[i]*rate[i]+b[i])。
ans[i]=max{x[j]*a[i]+y[j]*b[i],ans[i-1]}。(ans[i-1]是假如说该天最优是某一天往后的不买也不卖的情况,因为有时候并不是一定要买卖才是最优的。)
y[j]*b[i]=ans[i]-x[j]*a[i]
y[j]=x[j]*(-a[i]/b[i])+ans[i]/b[i]。
则我们要ans[i]最大,实际上就是在j<i的每一对(x[j],y[j])上画k=-a[i]/b[i]的直线,找最大的截距。这个一看就是凸包的套路。
但是关键是,(x[j],y[j])和直线k的斜率都是没有规律的(即没有递增或递减的情况),直接处理很麻烦。
我们考虑cdq分治。(一天为一个操作,一个操作里包含了该天的信息)
初始情况下,将所有的操作按斜率k来排序。当某一个子问题处理完毕后,将子问题内的所有操作按x坐标排序。这样,当我们分治处理时,[l,mid]内的所有操作都按x排序,[mid+1,r]内的所有操作都按斜率k排序了,当然也必须保证[l,mid]的操作顺序(即第几天)在[mid+1,r]之前。
Code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const double eps=1e-9;
int n;
double ans[100010];
struct node{double a,b,k,rate,x,y;int id;
}w[100010],q[100010];
int st[100010];
double K(int x,int y)
{if (!y) return -1e20;
if (fabs(w[x].x-w[y].x)<eps) return 1e20;
return (w[y].y-w[x].y)/(w[y].x-w[x].x);}
bool cmp1(node a,node b){return a.k<b.k;}
bool cmp2(node a,node b)
{return a.x<b.x;}//?
void cdq(int l,int r)
{
if (l==r)
{
ans[l]=max(ans[l],ans[l-1]);
w[l].y=ans[l]/(w[l].a*w[l].rate+w[l].b);
w[l].x=w[l].y*w[l].rate;
return;
}
int mid=(l+r)/2,js0=l,js1=mid+1,tp=0;
for (int i=l;i<=r;i++)
if (w[i].id<=mid) q[js0++]=w[i];else q[js1++]=w[i];
for (int i=l;i<=r;i++) w[i]=q[i];
cdq(l,mid);
for (int i=l;i<=mid;i++)
{
while (tp>1&&K(st[tp],st[tp-1])<K(i,st[tp-1])+eps) tp--;
st[++tp]=i;
}
int j=1;
for (int i=mid+1;i<=r;i++)
{
while (tp>1&&w[i].k>K(st[tp],st[tp-1])-eps) tp--;
ans[w[i].id]=max(ans[w[i].id],w[st[tp]].x*w[i].a+w[st[tp]].y*w[i].b);
}
cdq(mid+1,r);js0=l;js1=mid+1;
merge(w+l,w+mid+1,w+mid+1,w+r+1,q+l,cmp2);
for (int i=l;i<=r;i++) w[i]=q[i];
}
int main()
{
scanf("%d%lf",&n,&ans[0]);
for (int i=1;i<=n;i++)
{
scanf("%lf%lf%lf",&w[i].a,&w[i].b,&w[i].rate);w[i].id=i;
w[i].k=-1.0*w[i].a/w[i].b;
}
sort(w+1,w+n+1,cmp1);
cdq(1,n);
printf("%.3f",ans[n]);
}