1. 程式人生 > >BZOJ 1492 [NOI2007]貨幣兌換Cash:斜率優化dp + cdq分治

BZOJ 1492 [NOI2007]貨幣兌換Cash:斜率優化dp + cdq分治

turn inline problem class fin 復雜度 所有 stdio.h isp

傳送門

題意

初始時你有 $ s $ 元,接下來有 $ n $ 天。

在第 $ i $ 天,A券的價值為 $ A[i] $ ,B券的價值為 $ B[i] $ 。

在第 $ i $ 天,你可以進行兩種操作:

  • 賣出:將 $ %OP $ 的A券和 $ %OP $ 的B券兌換成人民幣,其中 $ OP $ 為 $ [0,100] $ 之間的任意實數
  • 買入:支付 $ IP $ 元,買入A、B券的總價值為 $ IP $ 元,且買入A、B券的數量之比為 $ Rate[i] $

人民幣和金券的數量可以為一個實數。一天可以進行多次操作。

問你 $ n $ 天後最多能獲得多少元錢。

$ (n \leq 10^5, 0 \leq A[i],B[i] \leq 10, 0 \leq Rate[i] \leq 100, ans \leq 10^9) $

題解

首先有一個顯然的結論:必定有一種最優方案,滿足每一天至多進行一種操作。

設 $ f[i] $ 表示在第 $ i $ 天能夠獲得的最大錢數,$ x[i], y[i] $ 分別表示第 $ i $ 天能夠獲得A、B券的最大數量。

則有:
\[ x[i] = \frac{f[i]}{A[i]+B[i]/Rate[i]} \]

\[ y[i] = \frac{f[i]}{A[i]*Rate[i]+B[i]} \]

?

然後考慮 $ f[i] $ 如何轉移。

對於第 $ i $ 天來說,要麽什麽都不做,要麽將之前某一天的金券在這一天賣掉。

所以:
\[ f[i] = max(f[i-1], x[j]*A[i]+y[j]*B[i]) \quad (1 \leq j<i) \]


然而樸素dp是 $ O(n^2) $ 的過不了……

?

先讓所有的 $ f[i] = max(f[i],f[i-1]) $

那麽剩下的就是 $ f[i] = max(x[j] \ast A[i]+y[j] \ast B[i]) \quad (1 \leq j<i) $

變形得:
\[ y[j] = -\frac{A[i]}{B[i]}*x[j] + \frac{f[i]}{B[i]} \]
上式是一條斜率為 $ -\dfrac{A[i]}{B[i]} ?$ 且過點 $ (x[j], y[j]) ?$ 的直線的斜截式。

由於我們想讓 $ f[i] $ 最大,並且因為 $ B[i] \geq 0 $ ,所以只要讓上式的截距 $ \dfrac{f[i]}{B[i]} $ 最大即可。

?

我們考慮用cdq轉移dp。

首先對於當前區間,按照 $ id $ 大小分成兩個區間。$ id $ 小的在左區間,否則在右區間,且分別在左右區間中 $ id $ 大小遞增(滿足了 $ id $ 小的更新 $ id $ 大的的答案)。

假設左邊遞歸處理後是以 $ x,y $ 分別為第一和第二關鍵字從小到大排好序的,右邊提前按照斜率 $ -\dfrac{A[i]}{B[i]} $ 從大到小排好序的。

對於左區間,我們可以先for循環掃一遍左區間求出一個上凸殼。

那麽右區間每個元素的答案,一定是取它與左邊凸殼相切時的截距(因為要讓截距最大)。

並且在依次求出右區間答案時,切點是不斷向右移動的(因為右區間中元素的斜率不斷減小)。

所以總復雜度就降為了 $ O(nlogn) $

AC Code

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#define MAX_N 100005
#define INF_LF 1e13
#define EPS 1e-8

using namespace std;

struct Data
{
    double a,b,r,x,y,k;
    int id;
    friend bool operator < (const Data &a,const Data &b)
    {
        return a.k>b.k;
    }
};

int n;
double f[MAX_N];
Data t[MAX_N];
Data c[MAX_N];
Data tmp[MAX_N];

inline double fabs(double x)
{
    return x>0 ? x : -x;
}

inline bool eq(double x,double y)
{
    return fabs(x-y)<=EPS;
}

inline double slope(Data a,Data b)
{
    if(eq(a.x,b.x)) return INF_LF;
    return (a.y-b.y)/(a.x-b.x);
}

inline bool cmp(Data a,Data b)
{
    if(a.x!=b.x) return a.x<b.x;
    return a.y<b.y;
}

void read()
{
    scanf("%d%lf",&n,&f[1]);
    for(int i=1;i<=n;i++)
    {
        scanf("%lf%lf%lf",&t[i].a,&t[i].b,&t[i].r);
        t[i].k=(eq(t[i].b,0) ? -INF_LF : -t[i].a/t[i].b);
        t[i].id=i;
    }
}

void cdq(int l,int r)
{
    if(l==r)
    {
        int id=t[l].id;
        f[id]=max(f[id],f[id-1]);
        t[l].x=f[id]/(t[l].a+t[l].b/t[l].r);
        t[l].y=f[id]/(t[l].a*t[l].r+t[l].b);
        return;
    }
    int mid=(l+r)>>1,tot=0;
    for(int i=l,j=mid+1,k=l;k<=r;k++)
    {
        if(t[k].id<=mid) tmp[i++]=t[k];
        else tmp[j++]=t[k];
    }
    for(int i=l;i<=r;i++) t[i]=tmp[i];
    cdq(l,mid);
    for(int i=l;i<=mid;i++)
    {
        while(tot>=2 && slope(c[tot-1],c[tot])<slope(c[tot],t[i])) tot--;
        c[++tot]=t[i];
    }
    int p=1;
    for(int i=mid+1;i<=r;i++)
    {
        while(p<tot && slope(c[p],c[p+1])>t[i].k) p++;
        f[t[i].id]=max(f[t[i].id],c[p].x*t[i].a+c[p].y*t[i].b);
    }
    cdq(mid+1,r);
    for(int i=l,j=mid+1,k=l;k<=r;k++)
    {
        if((i<=mid && cmp(t[i],t[j])) || j>r) tmp[k]=t[i++];
        else tmp[k]=t[j++];
    }
    for(int i=l;i<=r;i++) t[i]=tmp[i];
}

void work()
{
    sort(t+1,t+1+n);
    cdq(1,n);
    printf("%.3f\n",f[n]);
}

int main()
{
    read();
    work();
}

BZOJ 1492 [NOI2007]貨幣兌換Cash:斜率優化dp + cdq分治