题目描述
题解
首先可以发现,每一天的决策一定是全部买入或者全部卖出,因为只要有利益一定最大限度地取,如果有亏损就一点不碰
然后需要说的是,每一天所有的钱数可以唯一地转化为一种A券和B券的方案,也就是说,不用管某一天手里是有钱还是有券,反正都是等价的
令f(i)表示第i天的最大收益,显然f(0)=s
假设第i天手里有xi张A券,yi张B券,那么相对应地,第i天手里的钱就是f(i)=xi*Ai+yi*Bi
根据rate,xi和yi又满足xi/yi=ratei,代入之后第i天手里的钱就是f(i)=yi*ratei*Ai+yi*Bi
可以解出来yi=f(i)/(ratei*Ai+Bi),xi=f(i)*ratei/(ratei*Ai+Bi)
也就是说在某一天知道了f(i)或者xiyi中的任意一个都可以推出另外一个
那么我们现在目标就是最大的f(n),可以写出状态转移方程:f(i)=max{f(i-1),xj*Ai+yj*Bi}(1<=j<=i-1)
也就是说,第i天有两种选择:①将第j天换来的xj张A券和yj张B券卖出②什么都不做
那么我们可以先光考虑f(i)=max{xj*Ai+yj*Bi}(1<=j<=i-1)的这一种情况,把这个式子写成另外一种形式:yj=-(Ai/Bi)*xj+f(i)/Bi
这个式子就可以抽象成一条直线了,其中斜率为-(Ai/Bi),截距为f(i)/Bi,经过的点为(xj,yj),要使f(i)最大,实际上就是要使直线的截距最大
更形象地,我们现在有一条斜率为-(Ai/Bi)的直线,平面上有一些点,我们希望求出经过某一个点时,直线的纵截距最大
很显然最优答案一定是在平面上所有点的凸壳上,也就是将直线在坐标系中平移,在凸壳上卡住的那个点;那么对于每一个求出来的f(j),我们计算出来xj和yj,然后将点(xj,yj)插入平面,用splay维护凸包,然后每一次查询f(i)的时候就在凸包上二分一个将直线的斜率分开的点就行了
还有一种更好写好调的做法,就是cdq分治:因为i只会由1..i-1转移过来,所以可以递归左区间/处理左区间对右区间的影响/递归右区间来实现所有的转移
考虑如何处理左区间对右区间的影响:可以将左区间里对应的点求出来一个凸壳,然后将右区间的所有直线按照斜率递减排序,然后用一个指针在凸包上单调地扫一遍就行了
注意求凸壳一定要.先.按.照.x.坐.标.排.序.
代码
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
#define N 100005
const double inf=1e9;
const double eps=1e-9;
int dcmp(double x)
{
if (x<=eps&&x>=-eps) return 0;
return (x>0)?1:-1;
}
int n;
int stack[N];
double f[N];
struct data
{
double a,b,rate,k,x,y;
int id;
}q[N],a[N],b[N];
double getk(data a,data b)
{
if (dcmp(a.x-b.x)==0) return -inf;
return (a.y-b.y)/(a.x-b.x);
}
int cmpl(data a,data b)
{
return a.x<b.x;
}
int cmpr(data a,data b)
{
return a.k>b.k;
}
void cdq(int l,int r)
{
if (l==r)
{
f[l]=max(f[l],f[l-1]);
int k=q[l].id;
q[l].x=f[l]*q[l].rate/(q[l].rate*q[l].a+q[l].b);
q[l].y=f[l]/(q[l].rate*q[l].a+q[l].b);
return;
}
int mid=(l+r)>>1;
cdq(l,mid);
int acnt=0,bcnt=0;
for (int i=l;i<=mid;++i) a[++acnt]=q[i];
for (int i=mid+1;i<=r;++i) b[++bcnt]=q[i];
sort(a+1,a+acnt+1,cmpl);sort(b+1,b+bcnt+1,cmpr);
int top=0;
for (int i=1;i<=acnt;++i)
{
while (top>1&&dcmp(getk(a[i],a[stack[top]])-getk(a[stack[top]],a[stack[top-1]]))>=0)
--top;
stack[++top]=i;
}
int now=1;
for (int i=1;i<=bcnt;++i)
{
while (now<top&&dcmp(getk(a[stack[now]],a[stack[now+1]])-b[i].k)>=0)
++now;
int k=stack[now];
f[b[i].id]=max(f[b[i].id],a[k].x*b[i].a+a[k].y*b[i].b);
}
cdq(mid+1,r);
}
int main()
{
scanf("%d%lf",&n,&f[0]);
for (int i=1;i<=n;++i)
{
scanf("%lf%lf%lf",&q[i].a,&q[i].b,&q[i].rate);
q[i].id=i;
q[i].k=-q[i].a/q[i].b;
}
cdq(1,n);
printf("%.3lf\n",f[n]);
}