• [NOI2007]货币兑换 cdq分治,斜率优化


    [NOI2007]货币兑换

    LG传送门

    妥妥的(n log n)cdq做法。

    这题用cdq分治也可以(n log n)但是在洛谷上竟然比一些优秀的splay跑得慢真是见了鬼了看来还是人丑常数大的问题

    先推式子

    (这一段与其他题解不会有太多不同,已经了解了的同学可以略过,注意一下转移中(x)(k)表示什么就行了。)

    (f[i])表示到第(i)天最多有多少钱,(g[i])表示用第(i)天时的钱最多能买多少B券,易知(g[i] = frac {f[i]} {r[i] * a[i] + b[i]})

    得到转移:(f[i] = max { max limits _{j = 1} ^{i - 1} {g[j] * frac{b[i]} {a[i]} + r[j] * g[j]} * a[i], f[i - 1] }),外面的(max)可以单独判,里面的(max)可以看出是一个斜率优化的式子(把(frac {b[i]} {a[i]})视作(x),把(g[j])视作(k),把(r[j] * g[j])视作(b))。但是我们发现斜率(k)并不是单调的,所以传统的斜率优化就无法解决这个问题了。

    这时就衍生出两种写法了,一种是用splay维护凸包,一种是用cdq分治处理转移,我们要介绍的是后者。

    考虑分治

    对于任意一个(f[i]),我们只要考虑到所有(1 le j le i - 1)对它的影响就行了,cdq分治擅长处理这类问题。

    对于一段区间([l, r]),先递归左子区间([l, m]),保证([l, m])(f)(g)值都已经得到了;把左子区间按(k)递增排序,把右子区间按(x)递增排序,就可以按平时的斜率优化来(O(n))转移;再把右子区间按在原序列中的位置递增排序,然后递归右子区间,此时左子区间对右子区间的影响都已经被考虑完了;边界是(l == r),到这里我们可以发现(1)(i - 1)(i)的影响都已经被考虑过了,别忘了(f[i - 1])(f[i])的转移。

    这样做是(O(n (log n) ^ 2))的,让人有点不爽,事实上我们可以做到(O(n log n))

    怎样做到1个(log)

    事实上cdq分治本身是一个归并的过程,我们可以利用这个过程去掉排序的复杂度。

    我们希望拿到([l, r])这个区间的时候(x)是单调的,于是在外面把原序列按(x)递增排序;拿到一个(x)递增的区间后,我们希望在原序列中靠左的东西去到左子区间,于是我们把([l, r])扫一遍,把在原序列中位置(le m(m = l + r >> 1))的东西放左边,(ge m)的放右边,而且左右子区间对于(x)的单调性没有受到影响;我们要处理左边对右边的影响,于是先递归左子区间,再像平时一样斜率优化处理转移,然后递归右子区间;我们希望一个区间的左子区间递归回来的时候是对于(k)单调递增的,于是在最后对(k)做一遍归并排序。这样每一层递归是(O(n))的。

    奉上蒟蒻的大常数代码。

    //written by newbiechd
    #include <iostream>
    #include <iomanip>
    #include <algorithm>
    #define R register
    #define I inline
    #define D double
    using namespace std;
    const int N = 100003;
    const D eps = 1e-8;
    int q[N];
    D f[N], g[N];
    struct cash {
        int id;
        D a, b, r, x;
        cash() {}
        cash(int id, D a, D b, D r) : id(id), a(a), b(b), r(r), x(b / a) {}
        I int operator < (cash q) { return x != q.x ? x < q.x : id < q.id; }
    }p[N], b[N];
    I D cross(int u, int v) { return (p[u].r * g[p[u].id] - p[v].r * g[p[v].id]) / (g[p[v].id] - g[p[u].id]); }
    I D calc(int u, int v) { return g[p[u].id] * (p[v].x + p[u].r); }
    I void update(int u, D v) {
        if (f[p[u].id] < v)
            f[p[u].id] = v, g[p[u].id] = f[p[u].id] / (p[u].b + p[u].r * p[u].a);
    }
    void solve(int l, int r) {
        if (l == r) {
            update(l, f[p[l].id - 1]);
            return ;
        }
        R int m = (l + r) >> 1, i, h, t;
        for (h = l, t = m + 1, i = l; i <= r; ++i)
            p[i].id <= m ? b[h++] = p[i] : b[t++] = p[i];
        for (i = l; i <= r; ++i)
            p[i] = b[i];
        solve(l, m), h = 1, t = 0;
        for (i = l; i <= m; ++i) {
            while (h < t && cross(q[t], i) < cross(q[t - 1], i) + eps)
                --t;
            q[++t] = i;
        }
        for (; i <= r; ++i) {
            while (h < t && calc(q[h], i) < calc(q[h + 1], i) + eps)
                ++h;
            update(i, calc(q[h], i) * p[i].a);
        }
        solve(m + 1, r);
        for (h = l, t = m + 1, i = l; h <= m && t <= r; )
            g[p[h].id] < g[p[t].id] ? b[i++] = p[h++] : b[i++] = p[t++];
        while (h <= m)
            b[i++] = p[h++];
        while (t <= r)
            b[i++] = p[t++];
        for (i = l; i <= r; ++i)
            p[i] = b[i];
    }
    int main() {
        ios::sync_with_stdio(0);
        R int n, i;
        D a, b, r;
        cin >> n >> f[1];
        for (i = 1; i <= n; ++i)
            cin >> a >> b >> r, p[i] = cash(i, a, b, r);
        g[1] = f[1] / (p[1].r * p[1].a + p[1].b), sort(p + 1, p + n + 1),
            solve(1, n), cout << fixed << setprecision(3) << f[n];
        return 0;
    }
    
    
  • 相关阅读:
    BZOJ2023: [Usaco2005 Nov]Ant Counting 数蚂蚁
    BZOJ2044: 三维导弹拦截
    BZOJ2982: combination
    Solidity(address的四个方法)
    Solidity基础
    如何搭建以太坊的私有链
    挖矿
    智能合约
    密码学
    比特币
  • 原文地址:https://www.cnblogs.com/cj-chd/p/10451774.html
Copyright © 2020-2023  润新知