[NOI2007]货币兑换 题解
[NOI2007]货币兑换 题解
Problem
初始时有\(m\)的本金,已经知道之后\(n\)天内每一天\(A\)券与\(B\)券的价值,每一天可以买入或卖出金券。
卖出金券方法如下:自己提供一个\([0,100]\)内的实数\(OP\),意义为将\(OP\%\)的\(A\)券和\(OP\%\)的\(B\)券卖出。
买入金券方法如下:每一天给定一个\(R\),自己给出一个买入的金额\(IP\),满足买入\(A\)券与\(B\)券数量之比为\(R\),总价值为\(OP\)。
求\(n\)天后拥有的最大总价值。
Solution
首先不难想到一个这样的策略,卖出金券时,一定全部卖出是最优的,这个感觉比较显然,有了这个,\(OP\)的限制就不存在了。
考虑DP,我们可以得到一个这样的转移方程:
\(dp_i=\max(dp_{i-1},\max\limits_{j=0}^{i-1}\{A_i \times X_j+B_i \times Y_j\})\),其中\(X_i=\frac{R_i \times dp_i}{R_i \times A_i+B_i},Y_i=\frac{dp_i}{R_i \times A_i+B_i}\)。
表示第\(i\)天不操作,或者把第\(j\)天买入的金券全部卖出的最大价值。这是一个\(O(n^2)\)的算法,能拿到60pts。
考虑如何去优化,看见式子中含与\(i,j\)有关部分之积\(A_i \times dp_j\),发现这是一个典型的斜率优化。
忽略掉\(\max\)符号,改写一下式子:\(Y_j=-\frac{A_i}{B_i}X_j+\frac{dp_i}{B_i}\),斜率已知,目标是最大化截距,可以维护一个上凸壳。
但是,我们发现每一次加入的点\((X_i,Y_i)\)无论在横坐标上还是纵坐标上都不单调,无法简单的用单调队列维护,于是考虑用平衡树动态维护凸壳,来实现在任意位置插入及删除,总复杂度\(O(nlogn)\)。
Code
#include<bits/stdc++.h>
#define eps 1e-8
#define DB double
using namespace std;
int n,m;
DB dp[100005];
DB A[100005],B[100005],R[100005],X[100005],Y[100005];
struct Splay{
int root;
DB Lk[100005],Rk[100005];
int f[100005],son[100005][2];
inline int which(int x){
return son[f[x]][1]==x;
}
inline DB slope(int a,int b){
if(abs(X[a]-X[b])<eps)
return -DBL_MAX;
return (Y[a]-Y[b])/(X[a]-X[b]);
}
inline void rotate(int x,int &goal){
int fx=f[x],gx=f[fx],d=which(x);
if(fx==goal)
goal=x;
else
son[gx][which(fx)]=x;
son[fx][d]=son[x][d^1];son[x][d^1]=fx;
f[son[fx][d]]=fx;f[fx]=x;f[x]=gx;
return;
}
inline void splay(int x,int &goal){
while(x!=goal){
int fx=f[x];
if(fx!=goal)
rotate(which(fx)==which(x)?fx:x,goal);
rotate(x,goal);
}
return;
}
void insert(int x){
if(!root){
root=x;
return;
}
int now=root,fx=0;
while(now){
fx=now;
now=son[now][X[x]>X[now]+eps];
if(!now){
f[x]=fx;
son[fx][X[x]>X[fx]+eps]=x;
}
}
return;
}
int findpre(int x){
int now=son[x][0],ans=now;
while(now){
if(Lk[now]>=slope(x,now)+eps){
ans=now;
now=son[now][1];
}
else
now=son[now][0];
}
return ans;
}
int findsuf(int x){
int now=son[x][1],ans=now;
while(now){
if(Rk[now]+eps<=slope(x,now)){
ans=now;
now=son[now][0];
}
else
now=son[now][1];
}
return ans;
}
void Add(int x){
splay(x,root);
if(son[x][0]){
int now=findpre(x);
Lk[x]=Rk[now]=slope(x,now);
splay(now,son[x][0]);
son[now][1]=0;
}
else
Lk[x]=+DBL_MAX;
if(son[x][1]){
int now=findsuf(x);
Rk[x]=Lk[now]=slope(x,now);
splay(now,son[x][1]);
son[now][0]=0;
}
else
Rk[x]=-DBL_MAX;
if(Lk[x]+eps<=Rk[x]){
root=son[x][0];
f[son[x][0]]=0;
f[son[x][1]]=root;
son[root][1]=son[x][1];
Rk[root]=Lk[son[root][1]]=slope(root,son[root][1]);
}
return;
}
int find(DB k){
int now=root;
while(now){
if(Rk[now]+eps<=k&&k+eps<=Lk[now])
return now;
else if(k<Rk[now]+eps)
now=son[now][1];
else if(k+eps>Lk[now])
now=son[now][0];
}
return 0;
}
}T;
int main(){
scanf("%d",&n);
scanf("%d",&m);
for(register int i=1;i<=n;++i){
scanf("%lf",&A[i]);
scanf("%lf",&B[i]);
scanf("%lf",&R[i]);
}
dp[0]=m;
for(register int i=1;i<=n;++i){
int j=T.find(-A[i]/B[i]);
dp[i]=max(dp[i-1],A[i]*X[j]+B[i]*Y[j]);
Y[i]=dp[i]/(R[i]*A[i]+B[i]);X[i]=Y[i]*R[i];
T.insert(i);T.Add(i);
}
printf("%0.3lf\n",dp[n]);
return 0;
}