题目:
题解:
f[i]表示第i天最多能获得多少钱,那么f[0]=s
题目中有提示说要不全部买入要不全部卖出,其实比较好理解,因为能赚钱一定要尽量赚
首先考虑一个问题,如果某天赚到的钱知道了,能换成A券的数量a和能换成B券的数量b就确定了
a/b=rate[i]
a*A[i]+b*B[i]=f[i](A[i],B[i]表示第i天两种金券的价值)
那就考虑已知f[1..i-1]如何求f[i],我们可以枚举最后持有金券的是哪一天,通过这一天的钱数f[j],我们可以算出持有的金券a,b,则:
f[i]=a*A[i]+b*B[i];
这是个n^2的啊,如何优化呢?
b=-A[i]/B[i]*a+f[i]/B[i],可以看成是一个以a为自变量,b为因变量的直线方程
当求解f[i]的时候,A[i]和B[i]都是确定的,f[i]最大就是截距最大
a和b作为已经求出来的决策点,是已知的
相当于有一条斜率固定的直线去卡平面上的一些点,求能得到的最大截距
显然答案一定在上凸壳上,可是斜率什么的都是不单调的,点也是随便往里加的
splay维护凸包
那么对于每一个求出来的f(j),我们计算出来a和b,然后将点(a,b)插入平面,用splay维护凸包,然后每一次查询f(i)的时候就在凸包上二分一个将直线的斜率分开的点就行了
挖坑等计算几何
cdq分治
可以发现影响都是左边对右边的,有要求用这种数据结构来维护,考虑一下cdq分治吧
关键词:时间,斜率,对于已经查询过去的还有横坐标(a)
我们需要实现一个Solve(l,r)过程,让它可以求出f[l]…f[r]的所有值
仍然考虑从中间把序列分成两半,用左边的去更新右边的。
先调用Solve(l,mid)求出l..mid的所有f值,求出作为决策点的两种金券数量(a,b)
然后对这些决策点求上凸壳
将右边的所有待更新点按照直线斜率-A[i]/B[i]排序,这样卡的点一定是按照x坐标递增的,只需要一个指针就可以维护了,按照单调性在凸包上扫一遍
然后调用Solve(mid+1,r)
关于这个分治其实有一个小技巧,因为我们想要的是左边按照a排好序,右边按照k排好序,整体是时间序,这样直接分治不免要有sort的过程
但如果我们全部按照k排好序,在进入过程之前按照时间分左右排一下,在全部结束过程之后再按照a排序(因为ta都用完了只能成为别人的左边),这样就会免去sort,全部都是归并
#include <cmath>
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const double INF=1e9;
const int N=100005;
const double eps=1e-9;
struct hh{double A,B,rate,a,b,k;int id;}Q[N],jl[N];
double f[N];int stack[N],n;
int dcmp(double x)
{
if (x<=eps && x>=-eps) return 0;
return (x>0)?1:-1;
}
double getk(hh a,hh b)
{
if (dcmp(a.a-b.a)==0) return INF;
return (a.b-b.b)/(a.a-b.a);
}
void merge(int l,int r)
{
int mid=(l+r)>>1,t1=l,t2=mid+1;
for (int i=l;i<=r;i++)
if ((t1<=mid && dcmp(Q[t1].a-Q[t2].a)<0) || t2>r) jl[i]=Q[t1++];
else jl[i]=Q[t2++];
for (int i=l;i<=r;i++) Q[i]=jl[i];
}
void cdq(int l,int r)
{
if (l==r)
{
f[l]=max(f[l],f[l-1]);
Q[l].b=f[l]/(Q[l].rate*Q[l].A+Q[l].B); Q[l].a=Q[l].b*Q[l].rate;
return;
}
int mid=(l+r)>>1,t1=l,t2=mid+1,top=0;
for (int i=l;i<=r;i++)
if (Q[i].id<=mid) jl[t1++]=Q[i];
else jl[t2++]=Q[i];
for (int i=l;i<=r;i++) Q[i]=jl[i];
//按照时间分开,此时右边已经是想要的形式,左边会在cdq分治里完成
cdq(l,mid);
for (int i=l;i<=mid;i++)
{
while (top>1 && dcmp(getk(Q[stack[top]],Q[i]) - getk(Q[stack[top-1]],Q[stack[top]]))>=0) top--;
stack[++top]=i;
}
for (int i=mid+1;i<=r;i++)
{
while (top>1 && dcmp(getk(Q[stack[top]],Q[stack[top-1]]) - jl[i].k)<0) top--;
int j=stack[top];
f[jl[i].id]=max(f[jl[i].id],Q[j].a*jl[i].A+Q[j].b*jl[i].B);
}
cdq(mid+1,r); merge(l,r);//用完的用a排好
}
int cmp(hh a,hh b){return a.k<b.k;}
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].k=-Q[i].A/Q[i].B; Q[i].id=i;
}
sort(Q+1,Q+n+1,cmp);
cdq(1,n);
printf("%.3lf",f[n]);
}