【CDQ分治】NOI2007货币兑换Cash

本文介绍了如何利用CDQ分治方法解决NOI2007中的一道货币兑换题目。通过分析,提出了一种从动态规划到斜率优化再到CDQ分治的解决方案。首先,定义DP状态并给出转移方程。接着,按照特定顺序排列,并使用单调队列进行区间处理,确保左侧元素的优化。最后强调处理顺序的重要性,以保证计算出的DP值是最优的。
摘要由CSDN通过智能技术生成

分析:

非常经典的CDQ版题。。。不知道为什么之前没写博客。。现在补上。

首先,不难得到一个会T的DP,定义 DP[i] D P [ i ] 表示前i天能得到的最大钱数。

DP[i]=max{DP[j]Ai+DP[j]/ratejBi} D P [ i ] = m a x { D P [ j ] ∗ A i + D P [ j ] / r a t e j ∗ B i }
即表示在第j天把所有钱换成金券,在第i天再全部换成钱。

在贪心的思路下,这个应该还是算显然的。

然后考虑斜率优化,定义 j<k j < k j j 优于k
那么

DP[j]Ai+DP[j]/ratejBi>DP[k]Ai+DP[k]/ratekBi D P [ j ] ∗ A i + D P [ j ] / r a t e j ∗ B i > D P [ k ] ∗ A i + D P [ k ] / r a t e k ∗ B i

(DP[j]DP[k])Ai>(DP[j]/ratejDP[k]/ratek)Bi ( D P [ j ] − D P [ k ] ) ∗ A i > − ( D P [ j ] / r a t e j − D P [ k ] / r a t e k ) ∗ B i

AiBi>(DP[j]/ratejDP[k]/ratek)(DP[j]DP[k]) − A i B i > ( D P [ j ] / r a t e j − D P [ k ] / r a t e k ) ( D P [ j ] − D P [ k ] )

然而。。我们发现这个式子就非常恶心了。。。 AiBi A i B i 不是单调的!

所以呢,引入CDQ分治来解决。

首先,根据可以先根据 AiBi − A i B i 排个序。

然后呢,分段处理DP

每一段先按照 i i 归并排序。

然后用单调队列处理右半区间的所有DP值,此时可以保证左侧的AiBi升序,所以就用普通的斜率优化的格式就可以处理了。

对了,这题要注意必须先处理左半区间,再处理当前区间的转移,再处理右半区间。因为本题中Dp值需要先左边更新了右边,才能得到右边的位置真正的DP值。(即如果先处理右半区间,再处理当前区间的话,可能会使得处理右侧时的DP值不是最优的)

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<vector>
#include<cmath>
#define SF scanf
#define PF printf
#define MAXN 100010
#define INF 0x3FFFFFFF
#define EPS 1e-9
using namespace std;
int n;
double s;
struct node{
    double x,y,a,b,k,rate;
    int w,id;
    bool operator < (const node &a)const {
        return a.k<k;
    }
}p[MAXN],t[MAXN];
int st[MAXN],top;
double f[MAXN];
double getk(int a,int b){
    if(b==0)
        return -INF;
    if(fabs(p[a].x-p[b].x)<EPS) return INF;
    return (p[b].y-p[a].y)/(p[b].x-p[a].x);
}
void solve(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].rate*p[l].y;
        return ;
    }
    int l1,l2,mid=(l+r)>>1,j=1;
    l1=l;
    l2=mid+1;
    for(int i=l;i<=r;i++){
        if(p[i].id<=mid)
            t[l1++]=p[i];
        else
            t[l2++]=p[i];
    }
    for(int i=l;i<=r;i++)
        p[i]=t[i];
    solve(l,mid);
    top=0;
    for(int i=l;i<=mid;i++){
        while(top>1&&getk(st[top-1],st[top])<getk(st[top-1],i)+EPS)
            top--;
        st[++top]=i;
    }
    st[++top]=0;
    for(int i=mid+1;i<=r;i++){
        while(j<top&&getk(st[j],st[j+1])+EPS>p[i].k)
            j++;
        f[p[i].id]=max(f[p[i].id],p[st[j]].x*p[i].a+p[st[j]].y*p[i].b);
    }
    solve(mid+1,r);
    l1=l;
    l2=mid+1;
    for(int i=l;i<=r;i++){
        if(((p[l1].x<p[l2].x||(fabs(p[l1].x-p[l2].x)<EPS&&p[l1].y<p[l2].y))||l2>r)&&l1<=mid)
            t[i]=p[l1++];
        else
            t[i]=p[l2++];
    }
    for(int i=l;i<=r;i++)
        p[i]=t[i];
}
int main(){
    SF("%d%lf",&n,&f[0]);
    for(int i=1;i<=n;i++){
        SF("%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+1+n);
    //PF("{%.3f %.3f}\n",p[1].k,p[2].k);
    solve(1,n);
    PF("%.3lf",f[n]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值