[COCI 2025/2026 #2] Natjecanje 题解
Natjecanje 题解:随机化爬山“骗分”实战
前言
本题是 COCI 2025/2026 #2 的一道题目,考察了在网格图上的路径规划与匹配问题。虽然正解涉及复杂的带花树算法,但在特定数据范围内,随机化算法往往能取得意想不到的效果。
1. 题目简述
在一个 \(N \times M\) 的网格图中,有一个中转站 \(S\) 和 \(K\) 个包裹 \(X\)。 每次最多携带两个包裹,从 \(S\) 出发,将所有包裹送到指定位置并返回 \(S\)。 求最短的总时间。
数据范围:\(N, M \le 500\),\(K \le 67\)。
2. 思路分析
2.1 预处理距离
首先,这是一个网格图上的最短路问题。由于边权都是 1,我们可以使用 BFS 求出任意两点之间的最短距离。 我们需要求出:
- \(S\) 到任意包裹 \(X_i\) 的距离,记为 \(dist(S, X_i)\)。
- 任意两个包裹 \(X_i, X_j\) 之间的距离,记为 \(dist(X_i, X_j)\)。
这一步的时间复杂度是 \(O(K \cdot N \cdot M)\),对于 \(K=67\) 完全可以接受。
2.2 转化为匹配问题
如果不考虑“顺路带两个”,每次只送一个包裹,那么送第 \(i\) 个包裹的代价是 \(2 \times dist(S, X_i)\)(去程+回程)。
如果我们将包裹 \(i\) 和包裹 \(j\) 放在一趟送,路径是 \(S \to X_i \to X_j \to S\)(或者反过来),代价是 \(dist(S, X_i) + dist(X_i, X_j) + dist(X_j, S)\)。
相比于单独送这两个包裹,合并配送节省的时间为: \[ \text{Saving}(i, j) = (2 \times dist(S, X_i) + 2 \times dist(S, X_j)) - (dist(S, X_i) + dist(X_i, X_j) + dist(X_j, S)) \] 化简得: \[ \text{Saving}(i, j) = dist(S, X_i) + dist(S, X_j) - dist(X_i, X_j) \]
我们的目标是总时间最小,等价于节省的时间最大。 问题就转化为了:在 \(K\) 个点中,选出若干对 \((i, j)\) 进行匹配,使得 \(\sum \text{Saving}(i, j)\) 最大。
这是一个经典的一般图最大权匹配问题。
2.3 正解 vs 骗分
- 正解:一般图最大权匹配通常需要使用带花树算法 (Blossom Algorithm)。但是这个算法极其复杂,考场上很难写对,而且代码量巨大。
- 骗分技巧:观察数据范围 \(K \le 67\),这个规模虽然不能用状压 DP,但对于随机化算法来说是一个非常暧昧的范围。我们可以使用随机化爬山 (Randomized Hill Climbing) 或者 模拟退火 (Simulated Annealing) 来寻找近似最优解。
3. 核心技巧:随机化爬山 (Randomized Hill Climbing)
既然我们要把 \(K\) 个物品两两配对,我们可以把它们看作一个排列 \(P\)。
- \(P_0\) 和 \(P_1\) 配对
- \(P_2\) 和 \(P_3\) 配对
- …
- 如果 \(K\) 是奇数,最后一个落单(不节省时间)。
3.1 算法流程
- 初始状态:生成一个随机排列 \(P\)。
- 局部优化(爬山):
- 尝试交换排列中的任意两个元素 \(P_i, P_j\)。
- 计算交换后的总节省量
new_saving。 - 如果
new_saving > current_saving,说明这个交换更优,保留交换并更新当前解。 - 否则,撤销交换。
- 重复上述过程,直到无法通过交换任意两个元素来获得更优解(陷入局部最优)。
- 随机重启:
- 为了防止陷入局部最优解,我们在允许的时间内(比如 0.9秒),多次随机打乱序列,重新开始爬山过程。
- 记录所有轮次中的最大值。
3.2 代码实现细节
1 | // 预处理部分省略... |
4. 为什么能过?
- 状态空间结构:匹配问题的解空间通常比较“平滑”,局部最优解往往离全局最优解不远。
- 规模适中:\(K=67\) 意味着 \(O(K^2)\) 的交换尝试非常快。在 1 秒内,我们可以进行成千上万次完整的爬山过程。
- 覆盖率高:通过大量的随机重启,大概率能撞到全局最优解。
5. 总结
在 OI 比赛中,遇到“最大权匹配”或者复杂的组合优化问题,如果正解(如带花树、网络流)难以实现,随机化 + 贪心/爬山/退火 往往是性价比最高的策略。
记住这个模板:
while (clock() < limit)卡时。random_shuffle随机重启。swap+check局部贪心优化。
这套组合拳在 \(N\) 较小(\(N < 100\))的非精确覆盖类问题中有着惊人的杀伤力。
6. 完整代码
1 |
|
随机化算法是 OI 赛场上的奇兵,掌握它,你就能在绝境中找到生机。


![[COCI 2025/2026 #2] Natjecanje 题解](/img/natjecanje/header.jpg)


