【cdq分治】[Noi2007] bzoj1492 貨幣兌換Cash

題目點這裏


嗯。。倒騰了這麼久終於折騰出來了。。

對cdq有一點感覺了T_T當然。。還是很多不懂 = =

在雅禮寫cdq寫不出來的感覺實在是。。。。



首先這題有一個貪心 就是每天的買賣一定是100% 這樣才能保證收益最大

然後設x[i]表示第i天最多擁有A劵的數量 y[i]表示第i天最多擁有B劵的數量 f[i]表示第i天把劵全賣出去獲得的最大金錢數

那麼 f[i] = max{ f[i -1], x[j] * A[i] + y[j] * B[i] }  (j < i)

其中 x[j] = f[j] / (A[j] * rate[j] + B[j])   y[j] = x[j] * rate[j]

f[i -1]更新是指不賣也不買。。這個很好更新先不考慮

兩邊同時除以B[i](常數)  f[i] / B[i] = A[i] / B[i] * x[i] + y[i]  即 y[i] = -A[i] / B[i] * x[i] + f[i] / B[i]

令 k = -A[i] / B[i] 維護上凸殼即可


由於斜率不是單調的 所以我們需要一顆叫splay的植物來維護 _(:з)∠)_

據說很容易寫掛。。(沒寫捂臉)然後神奇的陳丹琪研究出了神奇的分治做法。。

定義solve(L, R)爲回答[L, R]區間的詢問 認爲處理完以後我們就能得到[L, R]中的x[i]和y[i] 並且已按照x爲第一關鍵字y爲第二關鍵字排好序

二分出Mid 首先對於[L, Mid]遞歸處理 那麼現在用[L, Mid]中的信息來更新[Mid + 1, R]這段區間的f[i]

此時的斜率依舊不是單調的 但是我們可以先把斜率sort一下那麼就能直接用單調隊列O(N)更新了

也就是說 對於每個區間 用左區間構造一次上凸殼 更新一遍右邊排好序的斜率

然後再遞歸處理[Mid + 1, R] 最後把左右區間按照之前定義所說的順序排好序(這一步是一個歸併過程 複雜度O(N))

這樣的做法 分治的複雜度是O(N(logN)^2)的 因爲每次在更新的時候還要排序一次斜率

實際上我們可以預先把斜率排序好 然後每次需要遞歸左區間的時候把原本在左區間的數揀出來 最後合起來


開始歸併寫掛了 = =一直查不出錯。。還有其實可以少寫一個結構體的 我只是爲了方便排序把d和p分開寫了

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
 
using namespace std;
 
const int Nmax = 1e5 + 5;
const double eps = 1e-9;
const double inf = 1e20;
 
int N;
double f[Nmax];
 
struct Days{
    int pos;
    double a, b, rate, k;
    bool operator < (const Days &_) const {
        return k < _.k;
    }
}d[Nmax], td[Nmax];
 
inline void update(double &a, double b) { if (b > a) a = b; }
inline double Fabs(double a) { return a > 0 ? a : -a; }
 
struct Point{
    double x, y;
    bool operator < (const Point &_) const {
        return x < _.x || (Fabs(x - _.x) < eps && y < _.y);
    }
}p[Nmax], tp[Nmax];
 
struct queue{
    int num[Nmax], head, tail;
     
    void init() { head = 0; tail = -1; num[0] = 0; }
    void push_back(int a){ num[++tail] = a; }
    void pop_back() { --tail; }
    void pop_front() { ++head; }
    int front() { return num[head]; }
    int second() { return num[head + 1]; }
    int back() { return num[tail]; }
    int before() { return num[tail - 1]; }
    int size() { return tail - head + 1; }
}q;
 
inline double getk(int i, int j)
{
    if (!j) return inf;
    if (!i || Fabs(p[i].x - p[j].x) < eps) return -inf;
    return (p[i].y - p[j].y) / (p[i].x - p[j].x);
}
 
inline void split(int L, int M, int R)
{
    int L1 = L, L2 = M + 1;
    for (int i = L; i <= R; ++i) {
        if (d[i].pos <= M) td[L1++] = d[i];
        else td[L2++] = d[i];
    }
    for (int i = L; i <= R; ++i) d[i] = td[i];
}
 
inline void merge(int L, int M, int R)
{
    int L1 = L, L2 = M + 1;
    for (int i = L; i <= R; ++i) {
        if ((p[L1] < p[L2] || L2 > R) && L1 <= M) tp[i] = p[L1++];
        else tp[i] = p[L2++];
    }
    for (int i = L; i <= R; ++i) p[i] = tp[i];
}
 
void solve(int L, int R)
{
    if (L == R) {
        update(f[L], f[L - 1]);
        p[L].y = f[L] / (d[L].a * d[L].rate + d[L].b);
        p[L].x = p[L].y * d[L].rate;
        return;
    }
     
    int M = (L + R) >> 1;
    split(L, M, R); solve(L, M);
     
    q.init();
    for (int i = L; i <= M; ++i) {
        while (q.size() > 1 && getk(q.before(), q.back()) <= getk(q.back(), i)) q.pop_back();
        q.push_back(i);
    }
    for (int i = R; i > M; --i) {
        while (q.size() > 1 && d[i].k <= getk(q.front(), q.second())) q.pop_front();
        update(f[d[i].pos], p[q.front()].x * d[i].a + p[q.front()].y * d[i].b);
    }
     
    solve(M + 1, R); merge(L, M, R);
}
 
int main()
{
    ios :: sync_with_stdio(false);
    cin >> N >> f[0];
    for (int i = 1; i <= N; ++i) {
        cin >> d[i].a >> d[i].b >> d[i].rate;
        d[i].k = -d[i].a / d[i].b;  d[i].pos = i;
    }
    sort(d + 1, d + N + 1);
    solve(1, N); printf("%.3f\n", f[N]);
    return 0;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章