LG4027 [NOI2007] 货币兑换
Problem
- 题意简述:
- 某金券交易所提供 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;
}

浙公网安备 33010602011771号