货币兑换【题解】

9 篇文章 0 订阅
5 篇文章 0 订阅

题面


大意:一开始你只有一些钱.你可以用你的所有的钱去买a、b两种东西.你也可以用所有的a、b两种东西去换成钱.所以在一种情况下,你的手里只有钱或者a、b两种东西.
每一天a,b都有自己的价值,买入a,b时,都有一个固定的比值.也就是说,一定量的钱在某一天就对应了一定量的东西.
每一天的操作不限次数.

sol

如果你知道这是一道斜率优化dp,那么就很简单了.
列出式子发现,斜率式子中k不单调,x不单调,所以肯定有两个log。本蒟蒻会用树套树(各种splay,segment_tree,tree_array)等,然后CDQ分治.
但这不是重点。
重点是精度问题
说一下大致流程(CDQ)。
首先,为了保证你的决策范围是你之前的某一天,所以你永远只要考虑左边对右边的贡献.
进入CDQ分治中的一层,先去左半边区间,回溯到这一层的时候,你已经得到了左半边区间的一个保存凸包的数组(其实现在不一定是凸包,但这时横坐标是递增的),然后一个一个拿出来,由于横坐标递增,你只要一个一个放入斜率单调递减的单调队列,这时,单调队列里保存了一个横坐标递增,斜率递减的上凸包,然后把右半边的dp数组拿出来,在这个凸包上二分找最优决策点,然后递归进入右半边的区间,然后回溯到这一层,由于左右两边的横坐标都有序,所以再扫一遍,把有序的左右两边合成一个有序的整体,然后返回上一层

code

#include<bits/stdc++.h>
#define eps 1e-8
using namespace std;
const int _ = 100010;
const double INF = 20000000;
double f[_],g[_];
struct node{
    double x,y;
}s[_],t[_];
int n,ss;
double A[_],B[_],Ra[_],MX;
double js(register node a,register node b){
    return (b.y-a.y)/(b.x-a.x);
}
bool cmp(node a,node b){
    return a.x-b.x<eps;
}
void CDQ(register int L,register int R){
    if(L==R){
        f[L]=max(f[L],f[L-1]);
        g[L]=f[L]/(B[L]+Ra[L]*A[L]);
        s[L].x=-g[L]*Ra[L];s[L].y=g[L];
        return;
    }
    register int mid= (L+R)>>1;
    CDQ(L,mid);
    t[L]=s[L];
    register int pin1=L,pin2=mid+1;
    for(register int i=L+1;i<=mid;++i){
        while((pin1>L&&eps<js(t[pin1],s[i])-js(t[pin1-1],s[i]) ) ){
            if(t[pin1].x-s[i].x==0&&s[i].y<t[pin1].y)
                break;
            --pin1;
        }
        if(fabs(s[i].x-t[pin1].x) < eps)continue;
        t[++pin1]=s[i];
    }
    for(register int i=mid+1;i<=R;++i){
        register int l=L,r=pin1,mmid;
        while(l<=r){
            mmid= (l+r)>>1;
            register double k1,k2,k3;
            if(mmid==L)
                k1=INF;
            else k1=js(t[mmid-1],t[mmid]);
            if(mmid==pin1)
                k3=-INF;
            else k3=js(t[mmid],t[mmid+1]);
            k2=A[i]/B[i];
            if(k2-k3>eps&&eps<k1-k2){
                f[i]=max(f[i],t[mmid].y*B[i]-t[mmid].x*A[i]);
                break;
            }
            if(eps<k3-k2)l=mmid+1;
            else r=mmid-1;
        }
    }
    CDQ(mid+1,R);
    pin1=L;
    register int ppl=L;
    for(;pin1<=mid;++pin1){
        while(pin2<=R&&eps<s[pin1].x-s[pin2].x)
            t[ppl++]=s[pin2++];
        t[ppl++]=s[pin1];
    }
    while(pin2<=R)t[ppl]=s[pin2],++pin2,++ppl;
    for(register int i=L;i<=R;++i)
        s[i]=t[i];
    return;
}
int main(){
    scanf("%d%d",&n,&ss);
    for(register int i=1;i<=n;++i)scanf("%lf%lf%lf",&A[i],&B[i],&Ra[i]);
    f[1]=ss;
    CDQ(1,n);
    printf("%.3lf\n",f[n]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值