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 的变化分别为:
假定在第一天时,用户手中有 100元 人民币但是没有任何金券。用户可以执行以下的操作:
注意到,同一天内可以进行多次操作。小Y是一个很有经济头脑的员工,通过较长时间的运作和行情测算,他已经知道了未来N天内的A券和B券的价值以及Rate。他还希望能够计算出来,如果开始时拥有S元钱,那么N天后最多能够获得多少元钱。
Input
输入第一行两个正整数N、S,分别表示小Y能预知的天数以及初始时拥有的钱数。接下来N行,第K行三个实数AK、BK、RateK,意义如题目中所述。
对于100%的测试数据,满足:0<AK≤10;0<BK≤10;0<RateK≤100;MaxProfit≤10^9。
【提示】
输入文件可能很大,请采用快速的读入方式。
必然存在一种最优的买卖方案满足:
每次买进操作使用完所有的人民币;
每次卖出操作卖出所有的金券。
Output
只有一个实数MaxProfit,表示第N天的操作结束时能够获得的最大的金钱数目。答案保留3位小数。
题目分析
注意到题面里面有一个十分有启发意义的提示
每次买进操作使用完所有的人民币;
每次卖出操作卖出所有的金券
设
为第
天最多能有多少钱
根据上面的提示,我们知道要使第
天的前尽量多
应该在第
天花光所有钱买入,再在第
天全部卖出,那么可以得到转移方程
其中
的转移表示第
天什么都不做
为方便表示,记
将上述转移方程
变形得
这个式子显然就是要我们斜率优化啊,也就是要维护一个上凸壳
但是
不单调,斜率
也不单调,普通的单调队列不能维护
所以选用动态性更强的平衡树来维护,一般选择Splay
或者用CDQ分治离线处理(然而蒟蒻不会)
Splay维护凸包
splay内的结点按
的大小 维护
并维护每个结点与左/右相邻结点的斜率
,及时删除不满足上凸壳的点
每次对于一条斜率为
的查询,查找一个满足
且
的点
来更新
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
using namespace std;
typedef long long lt;
typedef double dd;
#define eps 1e-9
int read()
{
int f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
const dd inf=1e9;
const int maxn=200010;
int n,m,rt;
int fa[maxn],ch[maxn][2];
dd lk[maxn],rk[maxn];
dd A[maxn],B[maxn],R[maxn];
dd P[maxn],Q[maxn];
dd dp[maxn];
void rotate(int& p,int x)
{
int y=fa[x],z=fa[y];
int d=(ch[y][0]==x);
if(y==p) p=x;
else if(ch[z][0]==y) ch[z][0]=x;
else ch[z][1]=x;
fa[y]=x; fa[ch[x][d]]=y; fa[x]=z;
ch[y][d^1]=ch[x][d]; ch[x][d]=y;
}
void splay(int& p,int x)
{
while(x!=p)
{
int y=fa[x],z=fa[y];
if(y!=p)
{
if((ch[y][0]==x)^(ch[z][0]==y)) rotate(p,x);
else rotate(p,y);
}
rotate(p,x);
}
}
dd calc(int j1,int j2)//计算斜率
{
if(P[j1]-P[j2]<eps&&P[j1]-P[j2]>-eps) return -inf;
else return (Q[j2]-Q[j1])/(P[j2]-P[j1]);
}
int pre(int x)
{
int u=ch[x][0],res=u;
while(u)
{
if(lk[u]+eps>=calc(u,x)) res=u,u=ch[u][1];
else u=ch[u][0];
}
return res;
}
int nxt(int x)
{
int u=ch[x][1],res=u;
while(u)
{
if(rk[u]<=calc(x,u)+eps) res=u,u=ch[u][0];
else u=ch[u][1];
}
return res;
}
int find(int x,dd K)
{
if(!x) return 0;
if(lk[x]>=K+eps&&rk[x]<=K+eps) return x;
else if(lk[x]<K+eps) return find(ch[x][0],K);
else return find(ch[x][1],K);
}
void update(int x)
{
splay(rt,x);
if(ch[x][0])
{
int lc=pre(x);//找到左边最后一个可以与x构成上凸壳的点
splay(ch[x][0],lc); ch[lc][1]=0;
lk[x]=rk[lc]=calc(lc,x);
}
else lk[x]=inf;
if(ch[x][1])
{
int rc=nxt(x);//找到右边第一个可以与x构成上凸壳的点
splay(ch[x][1],rc); ch[rc][0]=0;
lk[rc]=rk[x]=calc(x,rc);
}
else rk[x]=-inf;
if(lk[x]<=rk[x]+eps)//新加入的点不能与原来的点构成上凸壳,所以删除
{
rt=ch[x][0]; ch[rt][1]=ch[x][1];
fa[ch[x][1]]=rt; fa[rt]=0;
rk[rt]=lk[ch[rt][1]]=calc(rt,ch[rt][1]);
}
}
void ins(int &x,int pa,int u)
{
if(!x){ x=u; fa[x]=pa; return;}
if(P[u]<=P[x]+eps) ins(ch[x][0],x,u);//splay按x=Pi大小排序
else ins(ch[x][1],x,u);
}
int main()
{
scanf("%d%lf",&n,&dp[0]);
for(int i=1;i<=n;++i)
{
scanf("%lf%lf%lf",&A[i],&B[i],&R[i]);
int j=find(rt,-A[i]/B[i]);//找最优更新点
dp[i]=max(dp[i-1],A[i]*P[j]+B[i]*Q[j]);
Q[i]=dp[i]/(R[i]*A[i]+B[i]); P[i]=Q[i]*R[i];
ins(rt,0,i); update(i);//新的点插入splay维护凸包
}
printf("%.3lf",dp[n]);
return 0;
}
CDQ分治维护凸包
等蒟蒻会写了再补吧=_=