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
【提示】
输入文件可能很大,请采用快速的读入方式。
必然存在一种最优的买卖方案满足:
每次买进操作使用完所有的人民币;
每次卖出操作卖出所有的金券。
Output
只有一个实数MaxProfit,表示第N天的操作结束时能够获得的最大的金钱数目。答案保留3位小数。
题目分析
注意到题面里面有一个十分有启发意义的提示
每次买进操作使用完所有的人民币;
每次卖出操作卖出所有的金券
设 d p [ i ] dp[i] dp[i]为第 i i i天最多能有多少钱
根据上面的提示,我们知道要使第 i i i天的前尽量多
应该在第 j j j天花光所有钱买入,再在第 i i i天全部卖出,那么可以得到转移方程
d p [ i ] = m a x ( d p [ i − 1 ] , d p [ j ] ∗ R j R j ∗ A j + B j ∗ A i + d p [ j ] R j ∗ A j + B j ∗ B i ) , j ∈ [ 1 , i ) dp[i]=max(dp[i-1],\frac{dp[j]*R_j}{R_j*A_j+B_j}*A_i+\frac{dp[j]}{R_j*A_j+B_j}*B_i),j\in[1,i) dp[i]=max(dp[i−1],Rj∗Aj+Bjdp[j]∗Rj∗Ai+Rj∗Aj+Bjdp[j]∗Bi),j∈[1,i)
其中 d p [ i − 1 ] dp[i-1] dp[i−1]的转移表示第 i i i天什么都不做
为方便表示,记 X i = d p [ i ] ∗ R i R i ∗ A i + B i , Y i = d p [ i ] R i ∗ A i + B i X_i=\frac{dp[i]*R_i}{R_i*A_i+B_i},Y_i=\frac{dp[i]}{R_i*A_i+B_i} Xi=Ri∗Ai+Bidp[i]∗Ri,Yi=Ri∗Ai+Bidp[i]
将上述转移方程 d p [ i ] = X j ∗ A [ i ] + Y j ∗ B [ i ] dp[i]=X_j*A[i]+Y_j*B[i] dp[i]=Xj∗A[i]+Yj∗B[i]变形得
Y j = − A i B i X j + d p [ i ] B [ i ] Y_j=-\frac{A_i}{B_i}X_j+\frac{dp[i]}{B[i]} Yj=−BiAiXj+B[i]dp[i]
这个式子显然就是要我们斜率优化啊,也就是要维护一个上凸壳
但是 X i X_i Xi不单调,斜率 k = − A i B i k=-\frac{A_i}{B_i} k=−BiAi也不单调,普通的单调队列不能维护
所以用动态性更强的平衡树来维护,一般选择Splay
或者用CDQ分治离线处理也可以
Splay维护凸包
splay内的结点按 X i X_i Xi的大小 维护
并维护每个结点与左/右相邻结点的斜率 l k [ i ] , r k [ i ] lk[i],rk[i] lk[i],rk[i],及时删除不满足上凸壳的点
每次对于一条斜率为 K = − A i B i K=-\frac{A_i}{B_i} K=−BiAi的查询,查找一个满足 l k [ j ] > = K lk[j]>=K lk[j]>=K且 K < = r k [ j ] K<=rk[j] K<=rk[j]的点 j j j来更新 d p [ i ] dp[i] dp[i]
#include
#include
#include
#include
#include
#include
#include
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 X[maxn],Y[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(X[j1]-X[j2]<eps&&X[j1]-X[j2]>-eps) return -inf;
else return (Y[j2]-Y[j1])/(X[j2]-X[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);
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);
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(X[u]<=X[x]+eps) ins(ch[x][0],x,u);
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]*X[j]+B[i]*Y[j]);
Y[i]=dp[i]/(R[i]*A[i]+B[i]); X[i]=Y[i]*R[i];
ins(rt,0,i); update(i);
}
printf("%.3lf",dp[n]);
return 0;
}
CDQ分治维护凸包
先把每次需要查询的斜率按大小排序
CDQ分治处理区间 [ l l , r r ] [ll,rr] [ll,rr]时,先利用归并按询问时间为第二关键字对该区间排序
先向下递归处理左子区间
处理完后左子区间后构造左子区间所有点构成的上凸壳
利用该上凸壳处理右子区间的询问
由于斜率、询问时间有序,所以可以直接用栈维护
处理完右子区间的询问后递归处理右子区间
两个子区间处理完毕后对 区间 [ l l , r r ] [ll,rr] [ll,rr]按 X i X_i Xi排序,以便父区间构造凸壳
#include
#include
#include
#include
#include
#include
#include
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;
struct node{dd A,B,k,R,X,Y;int id;}Q[maxn],tt[maxn];
bool cmp(node a,node b){return a.k<b.k;}
dd dp[maxn];
int st[maxn];
dd calc(int j1,int j2)
{
if(fabs(Q[j1].X-Q[j2].X)<eps) return -inf;
else return (Q[j2].Y-Q[j1].Y)/(Q[j2].X-Q[j1].X);
}
void merge(int ll,int rr)
{
int mid=ll+rr>>1,t1=ll,t2=mid+1,p=ll;
while(t2<=rr)
{
while(Q[t1].X<=Q[t2].X&&t1<=mid) tt[p++]=Q[t1++];
tt[p++]=Q[t2++];
}
while(t1<=mid) tt[p++]=Q[t1++];
while(t2<=rr) tt[p++]=Q[t2++];
for(int i=ll;i<=rr;++i) Q[i]=tt[i];
}
void CDQ(int ll,int rr)
{
if(ll==rr)
{
dp[ll]=max(dp[ll],dp[ll-1]);
Q[ll].Y=dp[ll]/(Q[ll].A*Q[ll].R+Q[ll].B);
Q[ll].X=Q[ll].Y*Q[ll].R;
return;
}
int top=0;
int mid=ll+rr>>1,t1=ll,t2=mid+1;
for(int i=ll;i<=rr;++i)
if(Q[i].id<=mid) tt[t1++]=Q[i];
else tt[t2++]=Q[i];
for(int i=ll;i<=rr;++i) Q[i]=tt[i];
CDQ(ll,mid);
for(int i=ll;i<=mid;++i)
{
while(top>=2&&calc(st[top-1],st[top])<calc(st[top],i)+eps) top--;
st[++top]=i;
}
for(int i=mid+1;i<=rr;++i)
{
while(top>=2&&calc(st[top-1],st[top])<=Q[i].k+eps) --top;
int j=st[top];
dp[Q[i].id]=max(dp[Q[i].id],Q[j].X*Q[i].A+Q[j].Y*Q[i].B);
}
CDQ(mid+1,rr); merge(ll,rr);
}
int main()
{
scanf("%d%lf",&n,&dp[0]);
for(int i=1;i<=n;++i)
{
scanf("%lf%lf%lf",&Q[i].A,&Q[i].B,&Q[i].R);
Q[i].k=-Q[i].A/Q[i].B; Q[i].id=i;
}
sort(Q+1,Q+1+n,cmp); CDQ(1,n);
printf("%.3lf",dp[n]);
return 0;
}