[bzoj1492]NOI2007 CASH (DP+CDQ/Splay)

本文介绍了一种解决动态规划(DP)问题的斜率优化方法,并提供了详细的数学推导和具体的代码实现。通过该方法可以高效地求解涉及最大值问题的DP题目。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目传送门

我们可以很快发现一个dp解法:可以发现答案一定是由所持有的卷的数目计算出来的,而卷的数目是可以转移的,所以令 f[i] 表示在第i天可以得到的B卷的数目的最大值,可以得到转移方程为: f[i]=max{ans,Rate[j]f[j]a[i]+f[j]b[i]}Rate,ans=max{ans,Rate[j]f[j]a[i]+f[j]b[i]}

分析对于i的两个决策j和k,可以发现决策j比决策k优当且仅当:
f[j]Rate[j]f[k]Rate[k]f[j]f[k]>b[i]a[i] 时,决策j比k优。
我们定义 slope(j,k) 表示这个式子的左侧, G[i]=f[i]Rate[i]
即, slope(j,k)>b[i]a[i] ,在二维平面上定义点 Xi=(fi,Gi) ,那么式子左侧就是个两个点之间的斜率。
我们就可以斜率优化啦:
1) 给一个点集中添加一个点。
2) 给定一个负数斜率 K ,询问所有斜率为K且过点集中任意点的直线在y轴上的截距的最大值。

dp的第二种解法:
我们令 f[i] 表示最优答案, x[i],y[i] 分别表示两种卷在i时刻的最大数量。
于是 f[i]=max{f[i1],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)来说,要使得 f[i] 最大,我们需要使得直线在y轴上的截距最大。
于是我们维护平面上的点的上凸包。
解法1:Splay
用splay把所有点维护起来,每个点记录和左右两边的点的斜率。
对于每次的询问,我们用一条斜率为 a[i]b[i] 的直线去切割这个凸壳,就可以找到最优的转移点。
对于每次插入点,首先取出 x[j] ,然后向两边找到它能作为凸包时需要连接的点,删去中间经过的点。若找不到,也就说明它在凸包内,是一个凹点,把它自己删除。
//注意下面代码为了写起来方便 x,y 与上面的方程解释是反的
//坑填好啦
//你问我CDQ在哪里? 当然是选择坑着啦

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
const double eps=1e-8;
const double inf=1e20;
double X[maxn],Y[maxn];

//splay
int fa[maxn],ch[maxn][2];
double lk[maxn],rk[maxn];
int root,sz;
//splay
inline bool get(int x){
    return (ch[fa[x]][1]==x);
}
inline void rotate(int x){
    int old=fa[x];int oldf=fa[old];int d=get(x);
    fa[ch[x][d^1]]=old;ch[old][d]=ch[x][d^1];
    fa[old]=x;ch[x][d^1]=old;
    fa[x]=oldf;
    if(oldf)
        ch[oldf][ch[oldf][1]==old]=x;
}
inline void splay(int x,int g=0){
    for(int y;(y=fa[x])!=g;rotate(x)){
        if(fa[y]!=g)
            rotate((get(x)==get(y))? y:x);
    }
    if(!g)
        root=x;
}
inline double getk(int j,int k){
    if(fabs(X[j]-X[k]<=eps)) return inf;
    return (Y[j]-Y[k])/(X[j]-X[k]);
}
inline int get_pre(){
    int p=ch[root][0],ret=p;
    while(p){
        if(getk(root,p)+eps>=lk[p])
            p=ch[p][0];
        else
            ret=p,p=ch[p][1];
    }
    return ret;
}
inline int get_suc(){
    int p=ch[root][1],ret=p;
    while(p){
        if(getk(p,root)<=rk[p]+eps)
            p=ch[p][1];
        else 
            ret=p,p=ch[p][0];
    }
    return ret;
}
inline void insert(int &r,int pre,int p){
    if(!r){
        r=p;
        fa[p]=pre;
        return;
    }
    if(X[p]<=X[r]+eps) insert(ch[r][0],r,p);
    else insert(ch[r][1],r,p);
}
inline void update(int p){
    splay(p);
    if(ch[p][0]){
        int l=get_pre();
        splay(l,p);
        ch[l][1]=0;
        lk[p]=rk[l]=getk(p,l); 
    }else lk[p]=inf;
    if(ch[p][1]){
        int r=get_suc();
        splay(r,p);
        ch[r][0]=0;
        rk[p]=lk[r]=getk(r,p);
    }else rk[p]=-inf;
    //删除凹点
    if(lk[p]<rk[p]+eps){
        root=ch[p][0];
        ch[root][1]=ch[p][1];
        fa[ch[p][1]]=root;
        fa[root]=0;
        rk[root]=lk[ch[p][1]]=getk(ch[root][1],root);
    }
}
inline int getpos(double k){
    int p=root;
    while(p){
        //切割到了凸包 
        if(lk[p]+eps>=k&&k+eps>=rk[p])
            break;
        if(lk[p]<k+eps)
            p=ch[p][0];
        else p=ch[p][1];
    }
    return p;
}
inline double getans(double a,double b){
    int p=getpos(-b/a);
    return Y[p]*a+X[p]*b;
}
double a,b,rate;
int n;double ans;
int main(int argc,const char * argv[]){
    scanf("%d%lf",&n,&ans);
    for(int i=1;i<=n;i++){
        scanf("%lf%lf%lf",&a,&b,&rate);
        ans=max(ans,getans(a,b));
        X[i]=ans/(a*rate+b);
        Y[i]=X[i]*rate;
        insert(root,0,i);//将X,Y所代表的点插入平衡树
        update(i);
    }
    printf("%.3lf",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值