BZOJ1492: [NOI2007]货币兑换Cash

先%CDQ…
首先需要分析出一个性质,如果某天决定买入,那么一定花掉当前拥有的所有钱,如果某天决定卖出,那么一定卖掉拥有的所有股票。
于是对dp方程推一推倒一倒啊就是经典的斜率优化了…
(其实一个月前做的题我现在懒得推了…>_<…为啥我还要写这篇博客?为啥平衡树维护凸包没人用set写呢!?)
set:(边界判起来真麻烦…>_<…不过比手写平衡树要好不少…)

#include<set>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define dl long double
#define lb lower_bound
//by:MirrorGray
using namespace std;
const int N=211111;
const dl eps=1e-25;
int D;
dl dp[N],ans;
double A[N],B[N],R[N];
struct node{
    dl x,y;int id;
    node(dl a=0,dl b=0,int c=0){
        x=a;y=b;id=c;
    }
};
multiset <node> s;

inline dl C(int i){
    return dp[i]/(A[i]*R[i]+B[i]);
}

inline dl rake(int x,int y){
    if(fabs(C(x)-C(y))<eps)return 1e18;
    return (C(x)*R[x]-C(y)*R[y])/(C(y)-C(x));
}

bool operator <(const node&a,const node&b){
    if(~D&1){
        if(a.x==b.x)return a.y-b.y<-eps;
        return a.x-b.x<-eps;
    }
    else{
        if(a.y==b.y)return a.x-b.x<-eps;
        return a.y-b.y<-eps;
    }
}

int main(){
    int n,ss;scanf("%d%d",&n,&ss);ans=ss;
    for(int i=1;i<=n;i++)scanf("%lf%lf%lf",&A[i],&B[i],&R[i]);
    dp[1]=ans;s.insert(node(C(1),1e18,1));
    for(int i=2;i<=n;i++){
        D=true;
        node it=*s.lb(node(0,B[i]/A[i],0));
        ans=dp[i]=max(ans,dp[it.id]/((dl)A[it.id]*R[it.id]+B[it.id])*((dl)A[i]*R[it.id]+B[i]));
        D=false;node tmp(C(i),0,i);
        while(s.lb(tmp)!=s.end()){
            if(s.lb(tmp)==--s.end())break;
            it=*s.lb(tmp);
            if(rake(i,it.id)+eps<it.y)break;
            s.erase(s.lb(tmp));
        }
        while(true){
            if(--s.lb(tmp)==s.begin())break;
            node t1=*--s.lb(tmp);it=*(--(--s.lb(tmp)));
            if(it.y>rake(t1.id,i)+eps)s.erase(--s.lb(tmp));
            else break;
        }
        if(s.lb(tmp)==s.end())tmp.y=1e18;else tmp.y=rake(i,s.lb(tmp)->id);
        if(s.lb(tmp)!=s.begin()){
            it=*--s.lb(tmp);
            if(it.y>tmp.y)continue;
            s.erase(--s.lb(tmp));
            it.y=rake(it.id,i),s.insert(it);
        }
        s.insert(tmp);
    }
    printf("%.3f\n",(double)ans);
    return 0;
}

CDQ分治:

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define dl long double
//by:MirrorGray
using namespace std;
const int N=211111;
const dl eps=1e-20;
//重新理一下思路,先归并排序使得B[i]/A[i]单调,分治维护凸包
dl dp[N];
double A[N],B[N],R[N];
int s[N],ra[N][21],fz[N],a[N];

void sort(int l,int r,int d){
    if(l==r){
        ra[l][d]=l;return ;
    }
    int mid=(l+r)>>1;
    sort(l,mid,d+1);sort(mid+1,r,d+1);
    int t1=l,t2=mid+1,t=l;
    while(t<=r){
        if(t2>r||(t1<=mid&&B[ra[t1][d+1]]/A[ra[t1][d+1]]-B[ra[t2][d+1]]/A[ra[t2][d+1]]<-eps))
        ra[t++][d]=ra[t1++][d+1];
        else ra[t++][d]=ra[t2++][d+1];
    }
}

inline dl C(int i){
    return dp[i]/((dl)A[i]*R[i]+B[i]);
}

inline dl rake(int x,int y){
    if(fabs(C(x)-C(y))<eps)return 1e18;
    return (C(x)*R[x]-C(y)*R[y])/(C(y)-C(x));
}

void solve(int l,int r,int d){
    if(l==r){
        fz[l]=l;
        dp[l]=max(dp[l-1],dp[l]);
        return ;
    }
    int mid=(l+r)>>1;
    solve(l,mid,d+1);
    int top=0;
    for(int i=l;i<=mid;i++){
        while(top>1&&rake(fz[s[top]],fz[i])<rake(fz[s[top-1]],fz[s[top]])-eps)top--;
        //为什么我总是把符号弄反真是搞不懂…
        s[++top]=i;
    }
    int j=1;
    for(int i=mid+1;i<=r;i++){
        while(j<top&&rake(fz[s[j]],fz[s[j+1]])-B[ra[i][d]]/A[ra[i][d]]<-eps)j++;
        dp[ra[i][d]]=max(dp[ra[i][d]],C(fz[s[j]])*(A[ra[i][d]]*R[fz[s[j]]]+B[ra[i][d]]));
    }
    solve(mid+1,r,d+1);
    int t1=l,t2=mid+1,t=l;
    while(t<=r){
        if(t2>r||(t1<=mid&&C(fz[t1])<C(fz[t2])-eps))a[t++]=fz[t1++];
        else a[t++]=fz[t2++];
    }
    for(int i=l;i<=r;i++)fz[i]=a[i];
}

int main(){
    int n,s;scanf("%d%d",&n,&s);
    for(int i=1;i<=n;i++)scanf("%lf%lf%lf",&A[i],&B[i],&R[i]);
    sort(1,n,0);
    dp[1]=s;solve(1,n,1);
    printf("%.3f\n",(double)dp[n]);
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值