【BZOJ1492】[NOI2007] 货币兑换(斜率优化+CDQ分治)
大致题意: 有\(n\)天,起初有\(S\)元钱。每天有三个参数\(A_i,B_i,R_i\),表示当天\(A\)券的价格、当天\(B\)券的价格以及当天如果要买进金券\(A\)券和\(B\)券所需的比值。每天可以卖完所持金券,也可以用所有钱买入金券,求\(n\)天后能获得的最大钱数。
斜率优化
考虑\(DP\),设\(f_i\)为第\(i\)天结束时最大钱数。
方便起见,令\(x(i)\)为当天最大买入\(A\)券数\(R_i\times \frac{f_i}{R_iA_i+B_i}\),\(y_i\)为当天最大买入\(B\)券数\(\frac{f_i}{R_iA_i+B_i}\)。
那么就有一个暴力转移:
按照斜率优化的常见套路,我们求出当\(j\)的答案优于\(k\)的答案时需要满足的条件:
此时容易想到要把\(x_j-x_k\)移到右边去,为了保证符号不改变,强制\(x_j>x_k\),得到:
这样看起来似乎可以直接套路地单调队列优化\(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;
}