BZOJ1492 || 洛谷P4027 [NOI2007]货币兑换【斜率优化】【Splay/CDQ分治维护凸包】

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/niiick/article/details/86551094

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位小数。


题目分析

注意到题面里面有一个十分有启发意义的提示

每次买进操作使用完所有的人民币;
每次卖出操作卖出所有的金券

d p [ i ] dp[i] 为第 i i 最多能有多少钱
根据上面的提示,我们知道要使第 i i 天的前尽量多
应该在 j j 天花光所有钱买入,再在 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)
其中 d p [ i 1 ] dp[i-1] 的转移表示第 i i 天什么都不做

为方便表示,记 P i = d p [ i ] R i R i A i + B i , Q i = d p [ i ] R i A i + B i P_i=\frac{dp[i]*R_i}{R_i*A_i+B_i},Q_i=\frac{dp[i]}{R_i*A_i+B_i}
将上述转移方程 d p [ i ] = P j A [ i ] + Q j B [ i ] dp[i]=P_j*A[i]+Q_j*B[i] 变形得
Q j = A i B i P j + d p [ i ] B [ i ] Q_j=-\frac{A_i}{B_i}P_j+\frac{dp[i]}{B[i]}

这个式子显然就是要我们斜率优化啊,也就是要维护一个上凸壳
但是 x = P i x=P_i 不单调,斜率 k = A i B i k=-\frac{A_i}{B_i} 不单调,普通的单调队列不能维护
所以选用动态性更强的平衡树来维护,一般选择Splay
或者用CDQ分治离线处理(然而蒟蒻不会)


Splay维护凸包

splay内的结点按 x = P i x=P_i 的大小 维护
维护每个结点与左/右相邻结点的斜率 l k [ i ] , r k [ i ] lk[i],rk[i] ,及时删除不满足上凸壳的点
每次对于一条斜率为 K = A i B i K=-\frac{A_i}{B_i} 的查询,查找一个满足 l k [ j ] &gt; = K lk[j]&gt;=K K &lt; = r k [ j ] K&lt;=rk[j] 的点 j j 来更新 d p [ i ] dp[i]

#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分治维护凸包

等蒟蒻会写了再补吧=_=

猜你喜欢

转载自blog.csdn.net/niiick/article/details/86551094