大致题意: 有(n)天,起初有(S)元钱。每天有三个参数(A_i,B_i,R_i),表示当天(A)券的价格、当天(B)券的价格以及当天如果要买进金券(A)券和(B)券所需的比值。每天可以卖完所持金券,也可以用所有钱买入金券,求(n)天后能获得的最大钱数。
斜率优化
考虑(DP),设(f_i)为第(i)天结束时最大钱数。
方便起见,令(x(i))为当天最大买入(A)券数(R_i imes frac{f_i}{R_iA_i+B_i}),(y_i)为当天最大买入(B)券数(frac{f_i}{R_iA_i+B_i})。
那么就有一个暴力转移:
[f_i=min{A_i imes x_j+B_i imes y_j}
]
按照斜率优化的常见套路,我们求出当(j)的答案优于(k)的答案时需要满足的条件:
[A_ix_j+B_iy_j>A_ix_k+B_iy_k
]
[A_i(x_j-x_k)>B_i(y_k-y_j)
]
[-frac{A_i}{B_i} imes (x_j-x_k)<y_j-y_k
]
此时容易想到要把(x_j-x_k)移到右边去,为了保证符号不改变,强制(x_j>x_k),得到:
[frac{y_j-y_k}{x_j-x_k}>-frac{A_i}{B_i}
]
这样看起来似乎可以直接套路地单调队列优化(DP)了?
然而,并不行!
为什么呢?因为这题有两个东西都不满足单调性:
- (-frac{A_i}{B_i}):这个东西不满足单调性,因此你当前不优的答案不意味着以后不优,不能舍去。
- (x_i):这个才是关键,毕竟如果只是上面那家伙还可以用二分,可(x_i)不满足单调性,我们的转移条件又需要(x_i)是有序的,就非常麻烦了。
这样一来,似乎是要满足在区间中插入一个数,还要可以实现二分操作。
想到了什么?(Splay)!
但是写个数据结构毕竟太麻烦了,因此这里我们讨论一种较为简单的离线做法:(CDQ)分治。
(CDQ)分治
考虑我们以编号为分治区间,这样每次都满足左区间一定能向右区间转移。
然后看看我们要人为使其满足单调性的两个东西,恰好一个针对当前点((-frac{A_i}{B_i})),一个针对转移点((x_i))。
于是,我们把左区间按(x_i)排序,右区间按(-frac{A_i}{B_i})排序。
对于左区间,扫一遍,维护一个单调栈满足其斜率递减。
对于右区间,每次弹出单调栈顶部斜率小于等于(-frac{A_i}{B_i})的点,这样就可以保证栈顶一定是当前最优的转移点。
于是这道题就套路地做完了。
代码
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100000
#define DB double
#define eps 1e-8
#define max(x,y) ((x)>(y)?(x):(y))
using namespace std;
int n,m,S[N+5];DB a[N+5],b[N+5],R[N+5],f[N+5];
struct Point
{
DB x,y;I Point(Con DB& a=0,Con DB& b=0):x(a),y(b){}
I bool operator < (Con Point& o) Con {return fabs(x-o.x)>eps?x<o.x:y>o.y;}//按x排序
}p[N+5];
struct Data
{
int p;DB k;I Data(CI i=0,Con DB& t=0):p(i),k(t){}
I bool operator < (Con Data& o) Con {return k<o.k;}//按斜率排序
}s[N+5];
I void CDQ(CI l=1,CI r=n)//CDQ分治
{
if(l==r) return (void)(f[l]=max(f[l],m));RI i,mid=l+r>>1;CDQ(l,mid);//处理左区间
RI cnt=0;for(i=l;i<=mid;++i) p[++cnt]=Point(R[i]*f[i]/(R[i]*a[i]+b[i]),f[i]/(R[i]*a[i]+b[i]));//存点
RI tot=0;for(i=mid+1;i<=r;++i) s[++tot]=Data(i,-a[i]/b[i]);//存信息
#define K(j,k) (p[j].y-p[k].y)/(p[j].x-p[k].x)
RI T=0;for(sort(p+1,p+cnt+1),i=1;i<=cnt;++i)//求单调栈
{
if(i^1&&p[i].x-p[i-1].x<eps) continue;//特判x相同的情况
W(T>1&&K(i,S[T])>K(S[T],S[T-1])) --T;S[++T]=i;//维护单调栈单调性
}
for(sort(s+1,s+tot+1),i=1;i<=tot;++i)//动态规划
{
W(T>1&&K(S[T],S[T-1])<=s[i].k) --T;//弹走不优的解
f[s[i].p]=max(f[s[i].p],a[s[i].p]*p[S[T]].x+b[s[i].p]*p[S[T]].y);//DP转移
}
for(i=mid+1;i<=r;++i) f[i]=max(f[i],f[i-1]);CDQ(mid+1,r);//处理右区间
}
int main()
{
RI i;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%lf%lf%lf",a+i,b+i,R+i);
return CDQ(),printf("%.3lf",f[n]),0;
}