B3938 [语言月赛 202402] 陨石 题解

· · 题解

Source & Knowledge

2024 年 2 月语言月赛,由洛谷网校入门计划/基础计划提供。

题目大意

补给完成后,有 $m$ 块陨石分别撞击 $x_1, x_2, \cdots, x_n$ 号牛棚,每次撞击会令对应牛棚防御值减少 $2$ 点。当一间牛棚的防御值 $\leq 0$ 时,牛棚会被破坏。 目前想要让被破坏的牛棚尽可能少。求在最优的补给策略下,被破坏的牛棚的最少的数量。 ## 题目分析 不妨换一个角度来考虑。我们先让陨石撞击牛棚,计算出撞击后的牛棚的原始防御值,之后计算需要多少补给才能让某个牛棚不被破坏。即,我们计算每个牛棚的 $a_i - d_i$,并与 $1$($1$ 是牛棚不被破坏的最少防御值)做比较。其中 $d_i$ 代表每个牛棚因陨石而损失的防御值。 在计算时,可以直接枚举陨石,并直接在 $a$ 数组中做操作。核心代码如下: ```cpp for (int i = 1; i <= m; ++i) { int x; cin >> x; a[x] -= 2; } ``` 可以很显然地考虑到,当 $a_i - d_i < 1$ 时,我们需要补给这个牛棚,补给的数量是 $1 - (a_i - d_i)$。在补给有限的情况下,如果想要让尽可能多的牛棚存活,那显然需要**优先补给 $1 - (a_i - d_i)$ 更小**的牛棚。 因此,可以使用冒泡排序等方法,将 $1 - (a_i - d_i)$ 由小到大排序(或将 $(a_i - d_i)$ 由大到小排序也可,核心思路是一致的)。 ```cpp for (int i = 1; i <= n; ++i) { a[i] = 1 - a[i]; // 此处 a[i] 已经减去了损失值,直接覆写 // 代表需要的补给数量 } // 冒泡排序 for (int i = 1; i <= n; ++i) { for (int j = 1; j < n; ++j) { if (a[j] > a[j + 1]) swap(a[j], a[j + 1]); } } ``` 排序后,开始分配补给。从前往后枚举 $i$,判断每个牛棚的 $1 - (a_i - d_i)$ 是否大于 $0$(即需要补给)。如果小于等于 $0$,代表不需要补给,直接跳过;如果大于 $0$,判断当前剩余的补给是否够填补上 $1 - (a_i - d_i)$,如果足够则直接减去,否则中止补给。 在过程中,遇到「不需要补给」和「补给成功」的牛棚,同时计数即可。 ```cpp int ans = 0; for (int i = 1; i <= n; ++i) { // a[i] 代表需要的补给数量,上方已经覆写 if (a[i] <= 0) { // 不需要补给 ans++; } else { if (t >= a[i]) { // 剩余的 t 足够补给该牛棚 t -= a[i]; // 减去补给该牛棚的消耗 ans++; } else break; // 否则直接中断循环,因为后面的补给量只会越来越大,一定无法补给成功 } } cout << ans << endl; ``` ## 视频讲解 ![](bilibili:BV1JJ4m1s7yw?page=8)