题解:P16239 [蓝桥杯 2026 省 B] 足球训练

· · 题解

题意:

给定 npair<x,y>,限定你可以恰好做 m 次操作,每次是选一个 pair 执行 x+=y

最大化操作结束后的,这些 pairx 的乘积(\bmod 998,244,353)。

### 题解: 首先我们要先确定出给谁操作才是最优的,直接手玩样例是不简单的,其实我们可以用 $n=2$ 的小数据手玩。 我们可以先令 $m=1$,看能不能发现什么规律。 例如 $\{x=1,y=3\},\{x=1,y=100\}$,那我们肯定是操作后者,把第二个 `pair` 变成 $\{x=101,y=100\}$。 这样看上去似乎是操作 $x$ 比较大的。但当第二个 `pair` 换成 $\{x=100,y=100\}$ 的时候,此时我们发现操作第一个反倒更好。 如果你看懂了这个简单的例子,你就会发现我们每次操作的那个 `pair`,不一定非得要增量 $y$ 是最大的,而应该让**增幅**最大。 让我们用下面这个更一般化的例子看一看: 现在有 $n$ 个 `pair`,现在我们要决策当前这次操作给 $i=1$ 还是 $i=2$。也就是说,原本的乘积是:某定值 $S \times x_1\times x_2$(其中 $S$ 表示其余 $n-2$ 个 $x$ 的乘积),现在我们让 $x_1$ 变成 $x_1+y_1$,贡献变成了: $$ (x_1+y_1)\times x_2\times S $$ 如果我们给 $x_2$ 操作,那么贡献会反过来变成: $$ (x_2+y_2)\times x_1\times S $$ 假设最终给 $x_1$ 操作更好,也就是说:$(x_1+y_1)\times x_2\times S > (x_2+y_2)\times x_1\times S$ 的话,我们推导一下。 两边的 $S$ 显然可以直接约掉,然后把左边的 $x_2$ 除到右边,右边的 $x_1$ 除到左边,式子变成了: $$ \frac{x_1+y_1}{x_1} > \frac{x_2+y_2}{x_2} $$ 显然,化简后变成了: $$ \frac{y_1}{x_1} > \frac{y_2}{x_2} $$ 也就是说:**我们一定是把操作给到 $\frac{y}{x}$ 最大的那个 `pair` 上**。 于是我们简单得到了 $60$ 分做法,我们用优先队列存储所有的 `pair`,每次操作最优的进行贪心,维护时按照 $\frac{y}{x}$ 从大到小,因此用大根堆。(当然,这里为了避免精度问题,最好是把除法变成乘法) 但 $100$ 分怎么拿呢?这其实是非常经典的 trick,我们来介绍一下。 首先为了后面好处理,我们先把排序的规则改成按照 $\frac{x}{y}$ 从小到大(这和按 $\frac{y}{x}$ 从大到小本质一样)。 此时我们会发现,操作一定会选择 $\frac{x}{y}$ 最小的那个进行,于是:**在最优的 $m$ 次操作过程中,整个序列中 $n$ 个 `pair` 里的 $\frac{x}{y}$ 的最小值一定是单调不降的。因为我们每次选的是最小的那个操作,然后操作它之后,他的 $\frac{x}{y}$ 就会增大。** 于是,我们就可以考虑直接二分所有操作结束后的:$\min(\frac{x}{y})$ 的最大值。(二分最小值的最大值) 这样一来,对于某个 `pair`,如果其 $\frac{x}{y}$ 值小于我们二分出来的 $mid$,则我们就要不停操作这个 `pair`,直到它的值 $\geq mid$,这样一来,对于每个 `pair`,我们就可以在 $O(1)$ 的时间内把它应做的所有操作都做完。(简单推个式子即可) 于是我们以任意顺序处理完所有 $n$ 个 `pair` 即可,但这里又出现了新的问题,实际上有可能在所有 `pair` 都操作到不小于 $mid$ 后,$m$ 还可能有剩下的,这个问题出现的原因就好比:$\{2,2,2\}$ 和 $\{2,3,3\}$ 这两个数组的最小值都是 $2$ 一样。 但这时,我们一定可以证明的是,此时的 $m$ 在给所有 `pair` 都操作到其 $\frac{x}{y}\geq m$ 后,$m$ 一定严格小于 $n$。 这是因为,如果 $m\geq n$,那就意味着:我们至少可以把 $n$ 个 `pair` 每人都再操作一次,于是当前二分出来的 $mid$ 一定不是最优解对应的 $mid$。(最优解的 $mid$ 肯定能更大) **因此只要我们二分出来的 $mid$ 就是最优解对应的 $mid$,则 $m<n$ 一定成立。** 而我们发现此时 $m$ 其实已经非常小了,我们直接无脑套用 $60$ 分做法即可。(即优先队列 $O(m\times \log m)$ 模拟) 详见代码:(实现上有些细节,例如我们二分的本质上是浮点数,于是可以直接暴力二分 $100$ 次,这样能避免精度问题可能产生的死循环问题。) ```cpp struct Node { int x, y, id; bool operator<(const Node & n) const { int nx = n.x, ny = n.y; // nx / ny < x / y; return nx * y < x * ny; } }; void solve() { int n, m; cin >> n >> m; vector<int> a(n + 1), b(n + 1); for(int i = 1; i <= n; i++) { cin >> a[i] >> b[i]; } auto check = [&](double mid) { int C = 0; // 要使得所有 pair 的 x/y 不小于 mid,总共需要至少做 C 次操作 for(int i = 1; i <= n; i++) { double v = (double)a[i] / (double)b[i]; int cur = (int)ceil(mid - v); cur = max(0LL, cur); C += cur; if(C > m) return 0; } return 1; }; int cnt = 100; double l = 0, r = 2e14; while(cnt--) { double mid = (l + r) / 2; if(check(mid)) l = mid; else r = mid; } priority_queue<Node> q; for(int i = 1; i <= n; i++) { double v = (double)a[i] / (double)b[i]; int cur = (int)ceil(l - v); cur = max(0LL, cur); a[i] += cur * b[i]; m -= cur; q.push({a[i], b[i], i}); } while(m--) { auto [x, y, id] = q.top(); q.pop(); a[id] += y; q.push({a[id], y, id}); } int ans = 1; for(int i = 1; i <= n; i++) { assert(a[i] <= 1e18); a[i] %= MOD; ans = (ans * a[i]) % MOD; } cout << ans << endl; } ``` 时间复杂度:$O(n\times K)$。(其中 $K=100$ 为固定的二分次数)