[bzoj1492][NOI2007]货币兑换Cash

来自FallDream的博客,未经允许,请勿转载,谢谢。

题目好难解释啊,大家自己看原题吧??
小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天后最多能够获得多少元钱。
【提示】
必然存在一种最优的买卖方案满足:每次买进操作使用完所有的人民币;每次卖出操作卖出所有的金券。
n<=100000,0<AK≤10;0<BK≤10;0<RateK≤100;MaxProfit≤10^9。
 
题解:首先这个提示已经告诉了你这是一个dp啦。用f[i]表示前i天最多能获得的钱,那么假设这一天全部买进,那么获得的金券也就确定了。
n^2dp很simple,假设x[i],y[i]分别表示第i天最多买进的ab两种金券的数量,那么f[i]=max(f[i-1],x[j]*a[i]+y[j]*b[i])
考虑斜率优化,f[i]=max(x[j]*a[i]+y[j]*b[i]),y[j]=(-a[i]/b[i])*x[j]+f[i]/b[i],-a[i]/b[i]看作斜率,表示成了关于平面x,y上的斜率关系。bi不变,要让fi最大,就要让截距最大。
很容易发现,满足的点都在(x,y)的上凸壳上,所以我们维护这个凸壳。
 
怎么维护凸壳呢?下面介绍两种方法。
1)splay
由于xi不一定是连续的,所以不能用单调队列,所以用splay把所有点维护起来,每个点记录和左右两边的点的斜率。
每次查询,我们用一条(-ai/bi)的直线去切割这个凸壳,就可以找到最优的转移点。
每次插入,我们让按x坐标插入,然后往两边找到它能作为凸壳时连接的点,删去中间的点。假如找不到,也就说明它在凸壳内,把它自己删掉就行了。
这样我们就能在O(nlogn)的时间内解决该问题。
2)cdq分治
但是我们还有更简单的办法,那就是cdq分治。我发现我对于每个i做的事情,都是用它的斜率在前面找一个点来更新他,而复杂点在于x的无序,不能用单调队列维护。如果我能让x和查询的斜率变成有序的,我就可以用单调队列快速完成。很自然想到cdq分治,我保证每一段的斜率有序,在分治结束后,把它的坐标再归并起来,这样在对一段区间"治"的时候,左边的点有序,可以直接算出凸壳,右边的询问也有序,可以直接查询。
这样更新一段区间的复杂度是O(n)的,最多log层,加上初始的排序也是nlogn,总复杂度nlogn。
跑起来会比splay慢一些些,但是代码复杂度方面,稳胜splay
 
附上两种方法的代码 
splay维护凸壳:
#include<iostream>
#include<cstdio>
#include<cmath>
#define eps 1e-8
#define MN 100000
#define INF 2000000000
using namespace std;
inline int read()
{
    int x = 0 , f = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){ if(ch == '-') f = -1;  ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
    return x * f;
}

int fa[MN+5],c[MN+5][2],n,rt=0;
double a[MN+5],b[MN+5],x[MN+5],y[MN+5],r[MN+5],L[MN+5],R[MN+5],f[MN+5];

inline double getslop(int j,int k)
{
    if(fabs(x[j]-x[k])<eps)return -INF;
    return (y[j]-y[k])/(x[j]-x[k]);
}

void rotate(int x,int&k)
{
    int y=fa[x],z=fa[y],l=c[y][1]==x,r=l^1;
    if(y!=k) c[z][c[z][1]==y]=x;else k=x;
    fa[x]=z;fa[y]=x;fa[c[x][r]]=y;
    c[y][l]=c[x][r];c[x][r]=y;
}

void splay(int x,int&k)
{
    while(x!=k)
    {
        int y=fa[x],z=fa[y];
        if(y!=k)
        {
            if(c[y][1]==x^c[z][1]==y)rotate(x,k);
            else rotate(y,k);
        }
        rotate(x,k);
    }
}

int get(int x,double now)
{
    if(!x)return 0;
    if(L[x]+eps>=now&&now+eps>=R[x]) return x;
    return get(c[x][L[x]+eps>now],now);
}

void ins(int&now,int last,int k)
{
    if(!now){now=k;fa[k]=last;return;}
    ins(c[now][x[k]>x[now]+eps],now,k);
}

int ask_before(int x,int k)
{
    if(!x)return 0;int q;
    if(getslop(k,x)+eps>=R[x])return (q=ask_before(c[x][0],k))?q:x;
    else return ask_before(c[x][1],k);
}

int ask_after(int x,int k)
{
    if(!x)return 0;int q;
    if(getslop(x,k)<=L[x]+eps)return (q=ask_after(c[x][1],k))?q:x;
    else return ask_after(c[x][0],k);
}

void repair(int k)
{
    splay(k,rt);
    if(c[k][0])
    {
        int l=ask_before(c[k][0],k);
        if(l)
        {
            splay(l,c[k][0]);c[l][1]=0;
            R[l]=L[k]=getslop(k,l);
        }
        else L[k]=-INF;
    }
    else L[k]=INF;
    if(c[k][1])
    {
        int r=ask_after(c[k][1],k);
        if(r)
        {
            splay(r,c[k][1]);c[r][0]=0;
            L[r]=R[k]=getslop(r,k);
        }
        else R[k]=INF;
    }
    else R[k]=-INF;
    if(L[k]<=R[k]+eps)
    {
        int l=c[k][0],r=c[k][1];rt=l?l:r;
        fa[r]=l;c[l][1]=r;fa[l]=0;
        L[r]=R[l]=getslop(r,l);
    }
}

int main()
{
    n=read();scanf("%lf",&f[0]);
    for(int i=1;i<=n;i++)scanf("%lf%lf%lf",&a[i],&b[i],&r[i]);
    for(int i=1;i<=n;i++)
    {
        int j=get(rt,-(a[i]/b[i]));
        f[i]=max(f[i-1],a[i]*x[j]+b[i]*y[j]);
        y[i]=f[i]/(a[i]*r[i]+b[i]);
        x[i]=y[i]*r[i];
        ins(rt,0,i);repair(i);
    }
    printf("%.3lf",f[n]);
    return 0;
}

 cdq分治 好写多了。

#include<iostream>
#include<cstdio>
#include<cmath>
#include<algorithm>
#define eps 1e-8
#define MN 100000
#define INF 2000000000
using namespace std;
inline int read()
{
    int x = 0 , f = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){ if(ch == '-') f = -1;  ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
    return x * f;
}

struct P{
    double a,b,x,y,r,slop;int id;
    bool operator<(const P&b)const{return slop>b.slop;}
}p[MN+5],t[MN+5];
double f[MN+5];
int n,q[MN+5];

double getslop(int x,int y)
{
    if(!y)return -INF;
    if(fabs(p[x].x-p[y].x)<eps)return INF;
    return (p[y].y-p[x].y)/(p[y].x-p[x].x);
}

void solve(int l,int r)
{
    if(l==r)
    {
        f[l]=max(f[l-1],f[l]);
        p[l].y=f[l]/(p[l].a*p[l].r+p[l].b);
        p[l].x=p[l].y*p[l].r;
        return;
    }
    int mid=l+r>>1,cnt1=l,cnt2=mid+1,top=0,j=1;
    for(int i=l;i<=r;i++)
        t[p[i].id<=mid?cnt1++:cnt2++]=p[i];
    for(int i=l;i<=r;i++)p[i]=t[i];
    solve(l,mid);
    for(int i=l;i<=mid;i++)
    {
        while(top>1&&getslop(q[top-1],q[top])<getslop(q[top],i)+eps) --top;
        q[++top]=i;
    }
    for(int i=mid+1;i<=r;i++)
    {
        while(j<top&&getslop(q[j],q[j+1])+eps>p[i].slop) ++j;
        f[p[i].id]=max(f[p[i].id],p[q[j]].x*p[i].a+p[q[j]].y*p[i].b);
    }
    solve(mid+1,r);cnt1=l,cnt2=mid+1;
    for(int i=l;i<=r;i++)
        if(cnt2>r||(cnt1<=mid&&(p[cnt1].x<p[cnt2].x+eps
                 ||(fabs(p[cnt1].x-p[cnt2].x)<eps&&p[cnt1].y<p[cnt2].y+eps))))  t[i]=p[cnt1++];
        else t[i]=p[cnt2++];
    for(int i=l;i<=r;i++)p[i]=t[i];
}

int main()
{
    n=read();scanf("%lf",&f[0]);
    for(int i=1;i<=n;i++)
    {
        scanf("%lf%lf%lf",&p[i].a,&p[i].b,&p[i].r);
        p[i].slop=(-p[i].a/p[i].b);p[i].id=i;
    }
    sort(p+1,p+n+1);
    solve(1,n);
    printf("%.3lf\n",f[n]);
    return 0;
}

 

 
 
posted @ 2017-04-04 19:10  FallDream  阅读(841)  评论(0编辑  收藏  举报