[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;
}

转载于:https://www.cnblogs.com/cj-chd/p/10451774.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值