bsoj 2238 【NOI2007】货币兑换 (DP+斜率优化+splay维护)

【NOI2007】货币兑换

Time Limit:10000MS  Memory Limit:65536K
Total Submit:193 Accepted:50 
Case Time Limit:1000MS

Description

    
   

Input

   

Output

   

Sample Input

   

Sample Output

   

Hint

    
   

Source

xinyue

题目:http://mail.bashu.cn:8080/bs_oj/showproblem?problem_id=2238

题意:已知n天里两种债券的价值,还有每天可以买入的比例,求到第n天最多能获得的钱

分析:这题算是个经典题吧,具体的题解到处都是,这里就简单说说。。。

我们很容易想到一个转移方程,f[ i ]为第i天能获得的最多钱数,那么有f[ i ]=max{ f[ j ]/(r[ j ]*a[ j ]+b[ j ])*r[ j ]*a[ i ]+f[ j ]/(r[ j ]*a[ j ]+b[ j ])*b[ i ]}  1<=j<i

这样转移的复杂度为O(n^2)

我们容易联想到斜率优化之类的,那么我们试着假设 x= f[ j ]/(r[ j ]*a[ j ]+b[ j ])*r[ j ],   y= f[ j ]/(r[ j ]*a[ j ]+b[ j ])   ,G=a[ i ]*x +b[ i ]*y

那么有 y=-(a[i ]/b[i])*x+G/b[i] ,这个看起来可以用斜率优化来做,但是再仔细一看,x是无序的,y也是无序的,根本没办法用队列来维护一条凸线。。。

这时候splay之类的平衡树就派上用场了,我们可以用splay来维护这条上凸线 ^_^

具体先维护以x 大小为序的splay,对于每个节点,记录离他最近的左端点和右端点(待会维护凸线需要用到)

1.每次插入一个节点,先把它转为根节点,再往右边查找最近的满足凸线性质的节点(也就是 根->x->x最近的右端点向右拐),然后把它转到根节点下面,直接删掉其左子树。

往左边维护类似,由于这个新的节点可能不是凸线上的节点,再把它当前的左儿子转为根,做一次向右维护就行

2.每次查找值的话,先判断计算出离他最近的左右端点的值,往大的那边走就行,这里我遇到了问题,不知道是精度问题还是什么问题,有两组数据死活过不去,我加了一个判断,当前值大于最近的左端点和右端点的值,直接退出查找,去掉这句就AC,删了就wa= =

代码:

#include<cstdio>
#include<iostream>
using namespace std;
const int mm=111111;
double f[mm],a[mm],b[mm],r[mm],X[mm],Y[mm],tmp;
struct SplayTree
{
    int son[mm][2],far[mm],p[mm],tp[mm][2];
    int rt,size;
    void Link(int x,int y,int c)
    {
        far[x]=y,son[y][c]=x;
    }
    void Rotate(int x,int c)
    {
        int y=far[x];
        Link(x,far[y],son[far[y]][1]==y);
        Link(son[x][!c],y,c);
        Link(y,x,!c);
    }
    void Splay(int x,int g)
    {
        for(;far[x]!=g;)
        {
            int y=far[x],cx=son[y][1]==x,cy=son[far[y]][1]==y;
            if(far[y]==g)Rotate(x,cx);
            else
            {
                if(cx==cy)Rotate(y,cy);
                else Rotate(x,cx);
                Rotate(x,cy);
            }
        }
        if(!g)rt=x;
    }
    void NewNode(int y,int &x,int i)
    {
        x=++size;
        far[x]=y,p[x]=i;
        tp[x][0]=tp[x][1]=son[x][0]=son[x][1]=0;
    }
    void Insert(int i)
    {
        int x=rt,y,f;
        while(son[x][f=(X[p[x]]<X[i])])x=son[x][f];
        NewNode(x,son[x][f],i);
        y=tp[size][f]=tp[x][f];
        tp[y][!f]=tp[x][f]=size;
        tp[size][!f]=x;
        Splay(size,0);
        Maintain();
    }
    void Prepare()
    {
        NewNode(size=0,rt,1);
    }
    bool TurnRight(int a,int b,int c)
    {
        if(!a||!c)return 1;
        return (X[a]-X[b])*(Y[c]-Y[b])>(Y[a]-Y[b])*(X[c]-X[b]);
    }
    void Right()
    {
        int x=son[rt][1],y=rt;
        while(x)
        {
            if(TurnRight(p[rt],p[x],p[tp[x][1]]))y=x,x=son[x][0];
            else x=son[x][1];
        }
        if(y!=rt)
        {
            Splay(y,rt);
            son[y][0]=0;
            tp[rt][1]=y;
            tp[y][0]=rt;
        }
    }
    void Left()
    {
        int x=son[rt][0],y=rt;
        while(x)
        {
            if(TurnRight(p[tp[x][0]],p[x],p[rt]))y=x,x=son[x][1];
            else x=son[x][0];
        }
        if(y!=rt)
        {
            Splay(y,rt);
            son[y][1]=0;
            tp[rt][0]=y;
            tp[y][1]=rt;
        }
    }
    void Maintain()
    {
        if(son[rt][1])Right();
        if(son[rt][0])
        {
            Left();
            Splay(son[rt][0],0);
            Right();
        }
    }
    double Get(int i,int j)
    {
        return X[p[j]]*a[i]+Y[p[j]]*b[i];
    }
    double Find(int i)
    {
        int x=rt;
        double ret=Get(i,x),tmp1,tmp2;
        while(x)
        {
            x=son[x][Get(i,tp[x][0])<Get(i,tp[x][1])];
            ret=max(ret,Get(i,x));
        }
        if(x)Splay(x,0);
        return ret;
    }
}spt;
double get()
{
    char c;
    while((c=getchar())<'0'||c>'9');
    double a=c-'0',b=1;
    while((c=getchar())>='0'&&c<='9')a=a*10+c-'0';
    if(c=='.')
    while((c=getchar())>='0'&&c<='9')b*=10.0,a=a+(c-'0')/b;
    return a;
}
int i,n;
int main()
{
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    while(~scanf("%d%lf",&n,&f[1]))
    {
        for(i=1;i<=n;++i)
            a[i]=get(),b[i]=get(),r[i]=get();
        Y[1]=f[1]/(r[1]*a[1]+b[1]);
        X[1]=Y[1]*r[1];
        spt.Prepare();
        for(i=2;i<=n;++i)
        {
            f[i]=spt.Find(i);
            f[i]=max(f[i],f[i-1]);
            Y[i]=f[i]/(r[i]*a[i]+b[i]);
            X[i]=Y[i]*r[i];
            spt.Insert(i);
        }
        printf("%.3lf\n",f[n]);
    }
    return 0;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值