LG4027 [NOI2007] 货币兑换

Problem

LG4027 [NOI2007] 货币兑换

  • 题意简述:
    • 某金券交易所提供 A,B 两种金券。有一个用户初始有 \(s\) 元 RMB,在接下来的 \(n\) 天要进去交易最终获得尽量多的钱。
    • \(i\) 天,交易所给出三个参数 \(A_i,B_i,R_i\)\(A_i,B_i\) 表示 A,B 兑换成 RMB 的换算机制,\(R_i\) 为 RMB 兑换成同价值的 A,B 金券中 A,B 的比例。
    • 每一天的交易规则为:(这一部分结合了原题目中的【数据约定】的最后一行信息)
      • 卖出所有金券,设原来有 \(x\) A 券和 \(y\) B 券,增加 \(A_ix+B_iy\) RMB。
      • 花费所有钱买入金券,设原来有 \(x\) RMB,增加 \(\frac{xR_i}{A_iR_i+B_i}\) A 券和 \(\frac{x}{A_iR_i+B_i}\) B 券。
      • 不进行任何操作。
      • 同一天可以进行多次操作。
  • 数据约定:
    • \(1\le n\le 10^5,1\le S\le 10^9\)
    • \(0< A_i,B_i\le 10,0<R_i\le 100\) 且为实数。
    • \(\operatorname{MaxProfit}\le 10^9\)

Analysis

正解思路

这类带有操作性质且操作内有参数的题目,一般考虑都是递推(dp)解决。

然后我们自然会思考若某一天,\(A_i,B_i\) 很凑巧,我可以一直买入,卖出,买入,卖出……但其实中间不会有新的收益生成,就是等价变形。

\(dp_i\) 为前 \(i\) 天获得的最大收益。

考虑转移,第一种方式就是不卖金券,\(dp_i\gets dp_{i-1}\);第二种就是卖掉金券,枚举上一次买入金券的时候 \(j\)\(dp_j\) 元 RMB 在当时买入会产生 \(x_j=\frac{dp_jR_j}{A_jR_j+B_j}\) A 券和 \(y_j=\frac{dp_j}{A_jR_j+B_j}\) B 券,然后现在卖出:\(dp_i\gets \max_{1\le j<i}\{A_ix_j+B_iy_j\}\)

显然,转移是 \(O(n^2)\)

同样状态设计无法再优化,只能优化转移。

做法一

李超线段树优化。

将第二个转移化为 \(B_i\{x_j\frac{A_i}{B_i}+y_j\}\),括号内形如一个 \(kx+b\) 的一次函数(注意 \(k=x_j,b=y_j\)),可以套李超线段树。

时间复杂度为 \(O(n\log^2 n)\)

做法二

斜率优化 + cdq 分治。

有一种惯有思路是比较两个决策点(简化决策点数量),比如选取 \(j_1<j_2\)\(j_1\) 优于 \(j_2\) 的条件是 \(A_ix_{j_1}+B_iy_{j_1}>A_ix_{j_2}+B_iy_{j_2}\),化为不等式得 $$

做法三

平衡树优化。

错因总结

这题的一大难点在于把题读懂,而我正好陷入读题苦难中,特别是考虑是否可以一直买入,卖出。后面的优化我也不一定能想到。

AC Code

做法一

点击查看代码
#include <bits/stdc++.h>
#define ll long long 
#define ull unsigned long long 
#define i128 __int128
#define fi first
#define se second
#define PII pair<int, int>
#define PLL pair<ll, ll>
#define mk make_pair
#define INF 0x3f3f3f3f
#define INFx 0x3f3f3f3f3f3f3f3f
using namespace std;

const int N = 1e5 + 10;

int n, s;
double a[N], b[N], r[N];
double c[N], x[N], y[N];
vector<double> lsh;
double dp[N];
int tr[N << 2];

double f(int i, int j) {
    return x[j] * lsh[i - 1] + y[j];
}

void insert(int u, int l, int r, int p) {
    if (l == r) {
        if (f(l, p) > f(l, tr[u])) tr[u] = p;
    }
    else {
        int mid = l + r >> 1;
        if (f(mid, p) > f(mid, tr[u])) swap(p, tr[u]);
        if (f(l, p) > f(l, tr[u])) insert(u << 1, l, mid, p);
        else insert(u << 1 | 1, mid + 1, r, p);
    }
}

double query(int u, int l, int r, int p) {
    if (l == r) return f(l, tr[u]);
    else {
        int mid = l + r >> 1;
        double ans = f(p, tr[u]);
        if (p <= mid) ans = max(ans, query(u << 1, l, mid, p));
        else ans = max(ans, query(u << 1 | 1, mid + 1, r, p));
        return ans;
    }
}

int main() {
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);

    cin >> n >> s;
    
    for (int i = 1; i <= n; i ++) {
        cin >> a[i] >> b[i] >> r[i];
        c[i] = a[i] / b[i];
        lsh.push_back(c[i]);
    }

    sort(lsh.begin(), lsh.end());

    dp[0] = s;
    for (int i = 1; i <= n; i ++) {
        int p = lower_bound(lsh.begin(), lsh.end(), c[i]) - lsh.begin() + 1;
        dp[i] = max(dp[i - 1], b[i] * query(1, 1, n, p));
        x[i] = dp[i] * r[i] / (a[i] * r[i] + b[i]), y[i] = dp[i] / (a[i] * r[i] + b[i]);
        insert(1, 1, n, i);
    }

    cout << fixed << setprecision(3) << dp[n] << '\n';

    return 0;
}
posted @ 2026-02-25 16:16  KenopsiaMind  阅读(1)  评论(0)    收藏  举报