题解 P4027 [NOI2007] 货币兑换

view in Push_Y's blog

思路

贪心

首先很容易贪心的想到,最优的情况肯定是“买的时候把钱花完、卖的时候把券卖完”。

推柿子

记第 \(i\) 天结束时最大的钱数为 \(f_i\)

考虑通过输入给出的每组 \(A_i,B_i,R_i\),计算出第 \(i\) 天结束时所能买到的两种券的个数。

\[x_i=f_i \frac{R_i}{A_i R_i+B_i} \]

\[y_i=f_i \frac{1}{A_i R_i+B_i} \]

则有转移方程:

\[f_i=\max \{f_{i-1},\max \{x_j A_i+y_j B_i\} \} \]

转移到 \(i\) 时,决策 \(j_1\) 比决策 \(j_2\)(其中 \(j_1<j_2\))优等价于:

\[\frac{y_{j_1}-y_{j_2}}{x_{j_1}-x_{j_2}} > -\frac{A_i}{B_i} \]

不同于平常的斜率优化 dp,这时推出来的斜率不是一个定值。但如果在计算 \(i\) 时已经计算完成了 \(1\)\(i-1\),就可以用斜率优化了。于是想到了 cdq 分治。

cdq分治

考虑cdq分治传统步骤:

  1. cdq(l,mid)。

  2. 计算当前左半区间修改对右半区间询问的贡献。

  3. cdq(mid+1,r)。

考虑 2. 具体怎么做:按 \(-\frac{A_i}{B_i}\) 降序排序来保证之后查询凸壳时的复杂度。扫一遍左半部分,用单调队列的右指针维护上凸包(因为要使得斜率只降不升);扫一遍右半部分,每次移动左指针来更新当前最优的决策点。

由于分治递归左右区间的时候要保证时间是对的,递归之前要把左右区间区分开。

CODE

#include <bits/stdc++.h>
#define max(a,b) a>b?a:b
using namespace std;
inline int gin(){
    int s=0,f=1;
    char c=getchar();
    while(c<'0' || c>'9'){
        if(c=='-') f=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        s=(s<<3)+(s<<1)+(c^48);
        c=getchar();
    }
    return s*f;
}

const int N=1e5+5,inf=1e9;
int st[N],n;
double A[N],B[N],R[N],f[N],s;

struct node{
    int id;
    double x,y;
}t[N],tmp[N];
inline bool cmp(node a,node b){
    return -(A[a.id]/B[a.id]) > -(A[b.id]/B[b.id]);
}

double slope(int x,int y){
    if(t[x].x==t[y].x) return inf;
    return (t[y].y-t[x].y)/(t[y].x-t[x].x);
}

void cdq(int l,int r){
    if(l==r){
        f[l]=max(f[l],f[l-1]);
        t[l].x=f[l]/(A[l]*R[l]+B[l])*R[l];
        t[l].y=f[l]/(A[l]*R[l]+B[l]);
        return;
    }
    int mid=l+r>>1,p=l,q=mid+1;
    for(int i=l;i<=r;i++){//递归前需把左右区间区分开
        if(t[i].id<=mid) tmp[p++]=t[i];
        else tmp[q++]=t[i];
    }
    for(int i=l;i<=r;i++) t[i]=tmp[i];
    cdq(l,mid);
    q=0,p=1;
    for(int i=l;i<=mid;i++){//维护上凸包
        while(q>1 && slope(st[q-1],st[q])<slope(st[q],i)) q--;
        st[++q]=i;
    }
    for(int i=mid+1;i<=r;i++){
        while(p<q && slope(st[p],st[p+1])>-A[t[i].id]/B[t[i].id]) p++;//查询凸壳。如果没有预先按-A[i]/B[i]排序,这里每次二分查询是log的
        f[t[i].id]=max(f[t[i].id],t[st[p]].x*A[t[i].id]+t[st[p]].y*B[t[i].id]);
    }
    cdq(mid+1,r);
    p=l,q=mid+1;
    for(int i=l;i<=r;i++){
        if(q>r || p<=mid && t[p].x<t[q].x) tmp[i]=t[p++];
        else tmp[i]=t[q++];
    }
    for(int i=l;i<=r;i++) t[i]=tmp[i];
    return;
}

signed main(){
    n=gin();
    scanf("%lf",&s);
    for(int i=1;i<=n;i++){
        scanf("%lf%lf%lf",&A[i],&B[i],&R[i]);
        double x=s/(A[i]*R[i]+B[i])*R[i];
        double y=s/(A[i]*R[i]+B[i]);
        t[i]=(node){i,x,y};
        f[i]=s;
    }
    sort(t+1,t+n+1,cmp);
    cdq(1,n);
    printf("%.3lf\n",f[n]);
    return 0;
}
posted @ 2021-07-01 14:42  Push_Y  阅读(48)  评论(0编辑  收藏  举报