【斜率优化dp】【cdq分治】P4027 [NOI2007] 货币兑换

题意

较复杂

分析

f i f_i fi表示第i天卖出手中所有的券后的最大金额

贪心的考虑,如果有买卖操作一定是将所有的金额全部花完更优

第 i 天可以不经过卖的操作 f i = f i − 1 f_i=f_{i-1} fi=fi1
如果第 i 天卖了第 j 天买的 f i = a i x j + b i y j f_i=a_ix_j+b_iy_j fi=aixj+biyj
其中 x j , y j x_j,y_j xj,yj分别表示第 j 天按照比例购买的AB券数量

y j = − a i b i x + f i b i y_j=-\frac{a_i}{b_i}x+\frac{f_i}{b_i} yj=biaix+bifi,这样就是要最大化截距,但 x x x k k k都不单调

可以使用cdq分治来解决,先把所有点按照斜率从大到小排序

分治过程先递归处理左区间,然后归并把左侧区间按照 x x x排序,右侧区间按照 k k k的顺序,这样就可以直接建立凸包,单调队列就计算左区间对右区间的贡献,最后再递归处理右区间

细节:注意递归到 l = r l=r l=r的时候,要先更新 f i = f i − 1 f_i=f_{i-1} fi=fi1,同时 x , y x,y x,y也需要在return前计算

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+5;
const double eps=1e-10;
int n;
struct point
{
    double a,b,x,y,rate,k;
    int id;
}p[maxn],tmp[maxn];
double f[maxn];
bool cmp(point x,point y)
{
    return x.k>y.k;
}
int st[maxn];
double calc_k(int i,int j)
{
    if(fabs(p[j].x-p[i].x)<eps) return 1e18;
    return (p[j].y-p[i].y)/(p[j].x-p[i].x);
}
void cdq(int l,int r)
{
    if(l==r)
    {
        f[l]=max(f[l],f[l-1]);
        p[l].y=f[l]/(p[l].a*p[l].rate+p[l].b);
        p[l].x=p[l].y*p[l].rate;
        return;
    }
    int mid=l+r>>1;
    int tmpl=l-1,tmpr=mid;
    for(int i=l;i<=r;i++)
    {
        if(p[i].id<=mid) tmp[++tmpl]=p[i];
        else tmp[++tmpr]=p[i];
    }
    for(int i=l;i<=r;i++) p[i]=tmp[i];
    cdq(l,mid);
    int head=1,tail=0;
    for(int i=l;i<=mid;i++)
    {
        while(head<=tail && calc_k(st[tail-1],st[tail])<calc_k(st[tail-1],i)+eps)
            tail--;
        st[++tail]=i;
    }
    for(int i=mid+1;i<=r;i++)
    {
        while(head<tail && calc_k(st[head],st[head+1])+eps>p[i].k)
            head++;
        f[p[i].id]=max(f[p[i].id],p[st[head]].x*p[i].a+p[st[head]].y*p[i].b);
    }
    cdq(mid+1,r);
    tmpl=l; tmpr=mid+1;
    int now=l-1;
    while(tmpl<=mid && tmpr<=r)
    {
        if(p[tmpl].x<p[tmpr].x) tmp[++now]=p[tmpl++];
        else tmp[++now]=p[tmpr++];
    }
    for(int i=tmpl;i<=mid;i++) tmp[++now]=p[i];
    for(int i=tmpr;i<=r;i++) tmp[++now]=p[i];
    for(int i=l;i<=r;i++) p[i]=tmp[i];
}
int main()
{
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    scanf("%d%lf",&n,&f[0]);
    for(int i=1;i<=n;i++)
    {
        scanf("%lf%lf%lf",&p[i].a,&p[i].b,&p[i].rate);
        p[i].k=-p[i].a/p[i].b;
        p[i].id=i;
    }
    sort(p+1,p+n+1,cmp);
    cdq(1,n);
    printf("%.3f\n",f[n]);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值