Loading

【题解】P4027 [NOI2007] 货币兑换

题意好长,但是不想概括了。

\cdq/!

思路

cdq 分治 + 凸包。

首先考虑令 \(f_i\) 表示第 \(i\) 天可以得到的最大金额,\(f_1 = s\)

因为 \(rate_i\) 是常数,并且保证存在最优方案使得每次买进卖出都是全部金额,所以最优方案下第 \(i\) 天的 A 券和 B 券的比例是固定的,令第 \(i\) 天的 A 券有 \(x_i\) 个,B 券有 \(y_i\)

可以列方程组:

\[\begin{cases} \frac{x_i}{y_i} = rate_i \\ x_i A_i + y_i B_i = f_i \end{cases} \]

解得

\[\begin{cases} x_i = \frac{f_i \cdot rate_i}{a_i + rate_i b_i} \\ y_i = \frac{f_i}{a_i + rate_i b_i} \end{cases} \]

\(i\) 天卖出它们可以得到的金额是 \(x_i A_i + y_i B_i\),把 \(B_i\) 提出来:

\(B_i (\frac{A_i}{B_i} x_i + y_i)\)

括号里面是一个一次函数的形式,\(x_i\) 是斜率,\(y_i\) 是截距。

所以现在的问题变成了:

  1. 插入一条直线

  2. 求最高点

好家伙,没啥单调的东西,有人整了用 splay 维护的 \(O(n \log n)\) 好活,但我看不懂。

于是考虑用 cdq 分治进行动态转静态。

静态的情况下可以直接把所有转移点按照斜率排序构造凸包。

查询前先按 \(x\) 排序,又是可以指针维护的东西。

注意到转移对下标的前后顺序也有要求,这里可以先在 cdq 处理当前层的时候分拣出下标在中点两侧的元素,最后再归并起来,复杂度就优化了。

时间复杂度 \(O(n \log n)\),wonderful!

代码

#include <cstdio>
#include <algorithm>
using namespace std;

const int maxn = 1e5 + 5;

struct line
{
    double k, b, x;
} s[maxn], q[maxn], l;

struct item
{
    int p;
    double x;
} c[maxn], sr[maxn];

double interx(line& a, line& b) { return (a.k == b.k ? (a.b < b.b ? 1e12 : -1e12) : (b.b - a.b) / (a.k - b.k));}

int n, top;
double a[maxn], b[maxn], rate[maxn], f[maxn];

void solve(int l, int r)
{
    if (l == r)
    {
        f[l] = max(f[l], f[l - 1]);
        s[l].b = f[l] / (b[l] + rate[l] * a[l]);
        s[l].k = s[l].b * rate[l];
        return;
    }
    int mid = (l + r) >> 1, tl = l - 1, tr = 0;
    for (int i = l; i <= r; i++)
        if (c[i].p <= mid) c[++tl] = c[i];
        else sr[++tr] = c[i];
    for (int i = mid + 1; i <= r; i++) c[i] = sr[i - mid];
    solve(l, mid);
    top = 0;
    for (int i = l; i <= mid; i++)
    {
        while ((top > 1) && interx(s[i], q[top]) < q[top].x) top--;
        q[++top] = s[i];
        q[top].x = interx(s[i], q[top - 1]);
    }
    for (int i = mid + 1, p = 1; i <= r; i++)
    {
        while ((p < top) && (q[p + 1].x < c[i].x)) p++;
        int pos = c[i].p;
        f[pos] = max(f[pos], a[pos] * q[p].k + b[pos] * q[p].b);
    }
    solve(mid + 1, r);
    tr = mid + 1;
    top = 0;
    for (int i = l; i <= mid; i++)
    {
        while ((tr <= r) && (s[tr].k < s[i].k)) q[++top] = s[tr++];
        q[++top] = s[i];
    }
    while (tr <= r) q[++top] = s[tr++];
    for (int i = l; i <= r; i++) s[i] = q[i - l + 1];
}

bool cmp(const item& a, const item& b) { return (a.x < b.x); }

int main()
{
    // freopen("P4027_1.in", "r", stdin);
    scanf("%d%lf", &n, &f[1]);
    for (int i = 1; i <= n; i++)
    {
        scanf("%lf%lf%lf", &a[i], &b[i], &rate[i]);
        c[i] = (item){i, a[i] / b[i]};
    }
    sort(c + 1, c + n + 1, cmp);
    solve(1, n);
    printf("%.3lf\n", f[n]);
    return 0;
}
posted @ 2023-01-06 20:43  kymru  阅读(21)  评论(0编辑  收藏  举报