• 【BZOJ1492】[NOI2007] 货币兑换(斜率优化+CDQ分治)


    点此看题面

    大致题意:(n)天,起初有(S)元钱。每天有三个参数(A_i,B_i,R_i),表示当天(A)券的价格、当天(B)券的价格以及当天如果要买进金券(A)券和(B)券所需的比值。每天可以卖完所持金券,也可以用所有钱买入金券,求(n)天后能获得的最大钱数。

    斜率优化

    考虑(DP),设(f_i)为第(i)天结束时最大钱数。

    方便起见,令(x(i))为当天最大买入(A)券数(R_i imes frac{f_i}{R_iA_i+B_i})(y_i)为当天最大买入(B)券数(frac{f_i}{R_iA_i+B_i})

    那么就有一个暴力转移:

    [f_i=min{A_i imes x_j+B_i imes y_j} ]

    按照斜率优化的常见套路,我们求出当(j)的答案优于(k)的答案时需要满足的条件:

    [A_ix_j+B_iy_j>A_ix_k+B_iy_k ]

    [A_i(x_j-x_k)>B_i(y_k-y_j) ]

    [-frac{A_i}{B_i} imes (x_j-x_k)<y_j-y_k ]

    此时容易想到要把(x_j-x_k)移到右边去,为了保证符号不改变,强制(x_j>x_k),得到:

    [frac{y_j-y_k}{x_j-x_k}>-frac{A_i}{B_i} ]

    这样看起来似乎可以直接套路地单调队列优化(DP)了?

    然而,并不行!

    为什么呢?因为这题有两个东西都不满足单调性:

    • (-frac{A_i}{B_i}):这个东西不满足单调性,因此你当前不优的答案不意味着以后不优,不能舍去。
    • (x_i):这个才是关键,毕竟如果只是上面那家伙还可以用二分,可(x_i)不满足单调性,我们的转移条件又需要(x_i)是有序的,就非常麻烦了。

    这样一来,似乎是要满足在区间中插入一个数,还要可以实现二分操作。

    想到了什么?(Splay)

    但是写个数据结构毕竟太麻烦了,因此这里我们讨论一种较为简单的离线做法:(CDQ)分治。

    (CDQ)分治

    考虑我们以编号为分治区间,这样每次都满足左区间一定能向右区间转移。

    然后看看我们要人为使其满足单调性的两个东西,恰好一个针对当前点((-frac{A_i}{B_i})),一个针对转移点((x_i))。

    于是,我们把左区间按(x_i)排序,右区间按(-frac{A_i}{B_i})排序。

    对于左区间,扫一遍,维护一个单调栈满足其斜率递减。

    对于右区间,每次弹出单调栈顶部斜率小于等于(-frac{A_i}{B_i})的点,这样就可以保证栈顶一定是当前最优的转移点。

    于是这道题就套路地做完了。

    代码

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 100000
    #define DB double
    #define eps 1e-8
    #define max(x,y) ((x)>(y)?(x):(y))
    using namespace std;
    int n,m,S[N+5];DB a[N+5],b[N+5],R[N+5],f[N+5];
    struct Point
    {
    	DB x,y;I Point(Con DB& a=0,Con DB& b=0):x(a),y(b){}
    	I bool operator < (Con Point& o) Con {return fabs(x-o.x)>eps?x<o.x:y>o.y;}//按x排序
    }p[N+5];
    struct Data
    {
    	int p;DB k;I Data(CI i=0,Con DB& t=0):p(i),k(t){}
    	I bool operator < (Con Data& o) Con {return k<o.k;}//按斜率排序
    }s[N+5];
    I void CDQ(CI l=1,CI r=n)//CDQ分治
    {
    	if(l==r) return (void)(f[l]=max(f[l],m));RI i,mid=l+r>>1;CDQ(l,mid);//处理左区间
    	RI cnt=0;for(i=l;i<=mid;++i) p[++cnt]=Point(R[i]*f[i]/(R[i]*a[i]+b[i]),f[i]/(R[i]*a[i]+b[i]));//存点
    	RI tot=0;for(i=mid+1;i<=r;++i) s[++tot]=Data(i,-a[i]/b[i]);//存信息
    	#define K(j,k) (p[j].y-p[k].y)/(p[j].x-p[k].x)
    	RI T=0;for(sort(p+1,p+cnt+1),i=1;i<=cnt;++i)//求单调栈
    	{
    		if(i^1&&p[i].x-p[i-1].x<eps) continue;//特判x相同的情况
    		W(T>1&&K(i,S[T])>K(S[T],S[T-1])) --T;S[++T]=i;//维护单调栈单调性
    	}
    	for(sort(s+1,s+tot+1),i=1;i<=tot;++i)//动态规划
    	{
    		W(T>1&&K(S[T],S[T-1])<=s[i].k) --T;//弹走不优的解
    		f[s[i].p]=max(f[s[i].p],a[s[i].p]*p[S[T]].x+b[s[i].p]*p[S[T]].y);//DP转移
    	}
    	for(i=mid+1;i<=r;++i) f[i]=max(f[i],f[i-1]);CDQ(mid+1,r);//处理右区间
    }
    int main()
    {
    	RI i;for(scanf("%d%d",&n,&m),i=1;i<=n;++i) scanf("%lf%lf%lf",a+i,b+i,R+i);
    	return CDQ(),printf("%.3lf",f[n]),0;
    }
    
  • 相关阅读:
    【Android】Camera 使用浅析
    【Android】Camera 使用浅析
    每日学习总结<二> 2015-9-1
    每日学习总结<一> 2015-8-31
    【原创】利用typeface实现不同字体的调用显示及String转换为Unicode
    Android 软件开发之如何使用Eclipse Debug调试程序详解及Eclipse常用快捷键(转)
    Flask学习之 会话控制
    Vue组件介绍及开发
    Flask学习之 会话控制
    Flask学习之 请求与响应
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ1492.html
Copyright © 2020-2023  润新知