Description
小Y最近在一家金券交易所工作。该金券交易所只发行交易两种金券:A纪念券(以下简称A券)和 B纪念券(以下
简称B券)。每个持有金券的顾客都有一个自己的帐户。金券的数目可以是一个实数。每天随着市场的起伏波动,
两种金券都有自己当时的价值,即每一单位金券当天可以兑换的人民币数目。我们记录第 K 天中 A券 和 B券 的
价值分别为 AK 和 BK(元/单位金券)。为了方便顾客,金券交易所提供了一种非常方便的交易方式:比例交易法
。比例交易法分为两个方面:(a)卖出金券:顾客提供一个 [0,100] 内的实数 OP 作为卖出比例,其意义为:将
OP% 的 A券和 OP% 的 B券 以当时的价值兑换为人民币;(b)买入金券:顾客支付 IP 元人民币,交易所将会兑
换给用户总价值为 IP 的金券,并且,满足提供给顾客的A券和B券的比例在第 K 天恰好为 RateK;例如,假定接
下来 3 天内的 Ak、Bk、RateK 的变化分别为:
![BZOJ 1492: [NOI2007]货币兑换Cash 斜率优化 + splay动态维护凸包 BZOJ 1492: [NOI2007]货币兑换Cash 斜率优化 + splay动态维护凸包](https://image.miaokee.com:8440/aHR0cHM6Ly93d3cubHlkc3kuY29tL0p1ZGdlT25saW5lL3VwbG9hZC8yMDE2MDQvZGQoMSkucG5n.png?w=700&webp=1)
假定在第一天时,用户手中有 100元 人民币但是没有任何金券。用户可以执行以下的操作:
![BZOJ 1492: [NOI2007]货币兑换Cash 斜率优化 + splay动态维护凸包 BZOJ 1492: [NOI2007]货币兑换Cash 斜率优化 + splay动态维护凸包](https://image.miaokee.com:8440/aHR0cHM6Ly93d3cubHlkc3kuY29tL0p1ZGdlT25saW5lL3VwbG9hZC8yMDE2MDQvZGQoMikucG5n.png?w=700&webp=1)
注意到,同一天内可以进行多次操作。小Y是一个很有经济头脑的员工,通过较长时间的运作和行情测算,他已经
知道了未来N天内的A券和B券的价值以及Rate。他还希望能够计算出来,如果开始时拥有S元钱,那么N天后最多能
够获得多少元钱。
Input
输入第一行两个正整数N、S,分别表示小Y能预知的天数以及初始时拥有的钱数。接下来N行,第K行三个实数AK、B
K、RateK,意义如题目中所述。对于100%的测试数据,满足:0<AK≤10;0<BK≤10;0<RateK≤100;MaxProfit≤1
0^9。
【提示】
1.输入文件可能很大,请采用快速的读入方式。
2.必然存在一种最优的买卖方案满足:
每次买进操作使用完所有的人民币;
每次卖出操作卖出所有的金券。
Output
只有一个实数MaxProfit,表示第N天的操作结束时能够获得的最大的金钱数目。答案保留3位小数。
题解: 令 $f_{i}$ 表示第 $i$ 天所拥有的最大钱数
贪心猜到对于 $A,B$ 不是全部买进就是全部卖出
那么,第 $j$ 天所拥有的 $A$ 券 $x_{j}=\frac{f_{j}R_{j}}{a_{j}R_{j}+b_{j}}$, $B$ 券 $y_{j}=\frac{f_{j}}{a_{j}R_{j}+b_{j}}$
得 $f_{i}\Rightarrow x_{j}a_{i}+y_{j}b_{i}$
将方程写成一次函数的形式:$y_{j}=-\frac{a_{i}}{b_{i}}x_{j}+\frac{f_{i}}{b_{i}}$
对于 $i$来说 $a_{i},b_{i}$ 都是固定的,即斜率是一定的
将 $(x_{j},y_{j})$ 视为二维平面中的点,$f_{i}$ 最大化就是要让斜率为 $-\frac{a_{i}}{b_{i}}$ 的直线获得最大的截距
然而,斜率不是单调的,新加入点的横坐标($x_{i}$)也不是单调的,所以只能用平衡树来动态维护这个凸包.
需要支持插入一个点,查询一个直线所经过的最优点.
具体细节看代码,思路简单,代码不好写
那么,第 $j$ 天所拥有的 $A$ 券 $x_{j}=\frac{f_{j}R_{j}}{a_{j}R_{j}+b_{j}}$, $B$ 券 $y_{j}=\frac{f_{j}}{a_{j}R_{j}+b_{j}}$
得 $f_{i}\Rightarrow x_{j}a_{i}+y_{j}b_{i}$
将方程写成一次函数的形式:$y_{j}=-\frac{a_{i}}{b_{i}}x_{j}+\frac{f_{i}}{b_{i}}$
对于 $i$来说 $a_{i},b_{i}$ 都是固定的,即斜率是一定的
将 $(x_{j},y_{j})$ 视为二维平面中的点,$f_{i}$ 最大化就是要让斜率为 $-\frac{a_{i}}{b_{i}}$ 的直线获得最大的截距
然而,斜率不是单调的,新加入点的横坐标($x_{i}$)也不是单调的,所以只能用平衡树来动态维护这个凸包.
需要支持插入一个点,查询一个直线所经过的最优点.
具体细节看代码,思路简单,代码不好写
#include<bits/stdc++.h>
#define maxn 300000
#define inf 0x3f3f3f3f
#define setIO(s) freopen(s".in","r",stdin)
using namespace std;
const double eps = 1e-9;
int root;
struct Splaytree
{
#define get(x) (ch[f[x]][1]==x)
int cnt;
int f[maxn],ch[maxn][2];
double X[maxn],Y[maxn],lk[maxn],rk[maxn];
inline void rotate(int x)
{
int old=f[x],fold=f[old],which=get(x);
ch[old][which]=ch[x][which^1],f[ch[old][which]]=old;
ch[x][which^1]=old,f[old]=x,f[x]=fold;
if(fold) ch[fold][ch[fold][1]==old]=x;
}
inline void splay(int x,int &tar)
{
int fa,u=f[tar];
for(;(fa=f[x])!=u;rotate(x))
if(f[fa]!=u)
rotate(get(fa)==get(x)?fa:x);
tar=x;
}
inline double slope(int i,int j)
{
return fabs(X[i]-X[j])<=eps ? -inf : (Y[i]-Y[j])/(X[i]-X[j]);
}
inline void insert(int &o,double x,double y,int last)
{
if(!o)
{
o=++cnt;
X[o]=x,Y[o]=y,f[o]=last;
return;
}
insert(ch[o][x-X[o]>eps],x,y,o);
}
inline int pre(int x)
{
int cur=ch[x][0],re=0;
while(cur)
{
if(slope(x,cur)+eps>=rk[cur]) re=cur,cur=ch[cur][0];
else cur=ch[cur][1];
}
return re;
}
inline int nxt(int x)
{
int cur=ch[x][1],re=0;
while(cur)
{
if(slope(x,cur)<=lk[cur]+eps) re=cur,cur=ch[cur][1];
else cur=ch[cur][0];
}
return re;
}
inline int getl(int x)
{
while(ch[x][0]) x=ch[x][0];
return x;
}
inline int getr(int x)
{
while(ch[x][1]) x=ch[x][1];
return x;
}
inline void del(int x)
{
if(!ch[x][0])
{
int right=getl(ch[x][1]);
splay(right,ch[x][1]),root=right;
ch[x][1]=f[root]=0;
lk[root]=inf;
}
else if(!ch[x][1])
{
int left=ch[x][0];
splay(left,ch[x][0]),root=left;
ch[x][0]=f[root]=0;
rk[root]=-inf;
}
else
{
int right=getl(ch[x][1]);
int left=getr(ch[x][0]);
splay(left,ch[x][0]);
splay(right,ch[x][1]);
root=left;
ch[root][1]=right,f[right]=root;
rk[root]=lk[right]=slope(root,right);
}
}
inline void maintain(int x)
{
splay(x,root);
if(ch[x][0])
{
int left=pre(x);
if(left)
{
splay(left,ch[x][0]);
ch[left][1]=f[ch[left][1]]=0;
rk[left]=lk[x]=slope(left,x);
}
else lk[x]=-inf;
}
else lk[x]=inf;
if(ch[x][1])
{
int right=nxt(x);
if(right)
{
splay(right,ch[x][1]);
ch[right][0]=f[ch[right][0]]=0;
rk[x]=lk[right]=slope(right,x);
}
else rk[x]=inf;
}
else rk[x]=-inf;
if(lk[x]-rk[x]<=eps) del(x);
}
inline int getans(int x,double k)
{
if(!x) return 0;
if(lk[x]+eps>=k&&rk[x]<=k+eps) return x;
if(lk[x]<k+eps) return getans(ch[x][0],k);
else return getans(ch[x][1],k);
}
}splay;
int n,S;
double f[maxn],A[maxn],B[maxn],rate[maxn];
int main()
{
int i,j;
// setIO("input");
scanf("%d%lf",&n,&f[0]);
for(i=1;i<=n;++i)
{
scanf("%lf%lf%lf",&A[i],&B[i],&rate[i]);
int j=splay.getans(root,-(A[i]/B[i]));
double x=splay.X[j],y=splay.Y[j];
f[i]=max(f[i-1],A[i]*x+B[i]*y);
y=f[i]/(A[i]*rate[i]+B[i]);
x=y*rate[i];
splay.insert(root,x,y,0);
splay.maintain(i);
}
printf("%.3lf",f[n]);
return 0;
}