P9431 [NAPC-#1] Stage3 - Jump Refreshers 解题报告
P9431 [NAPC-#1] Stage3 - Jump Refreshers 解题报告
1. 读懂题目:我们要解决什么问题?
想象一下,你是一个游戏高手 "kid",在一个二维世界里玩跳跃游戏。这个世界里有 \(n\) 个“跳跃球”。
游戏规则是这样的:
- 起点: 你从第 \(c\) 个跳跃球开始。
- 跳跃: 当你站在一个跳跃球上时,你可以选择“跳一下”。这一跳会让你瞬间垂直向上移动 \(d\) 个单位。
- 下落与移动: 跳起来后,你就处于空中。接下来,每一秒钟会发生两件事:
- 你会自动往下掉 1 个单位。
- 你可以选择向左移动 1 格、向右移动 1 格,或者原地不动。
- 再次跳跃: 只有当你再次精确地落在一个跳跃球上时,你才能进行下一次跳跃。
- 目标: 你的“得分”是你从多少个不同的跳跃球上成功起跳过。你的目标就是让这个得分最大化。
- 重要提示: 跳跃球可以无限次使用。
简单来说,我们要找一条从起点开始的路径,这条路径能让我们在尽可能多的不同跳跃球上进行跳跃。
2. 问题转化:从物理跳跃到图论模型
直接模拟 kid 的每一步移动(每秒的下落和左右移动)太复杂了,我们需要一个更宏观的视角。
这个问题的核心是“从一个跳跃球 A 能否到达另一个跳跃球 B”。如果我们能回答这个问题,就可以把整个地图看作一张关系网络图。
- 节点(Vertices): 每个跳跃球都是图中的一个节点。
- 有向边(Directed Edges): 如果我们可以从跳跃球 \(i\) 出发,经过一次跳跃和随后的下落移动,最终能到达跳跃球 \(j\),我们就在图中画一条从 \(i\) 指向 \(j\) 的有向边
i -> j
。
那么,如何判断 i -> j
这条边是否存在呢?
假设跳跃球 \(i\) 的坐标是 \((x_i, y_i)\),跳跃球 \(j\) 的坐标是 \((x_j, y_j)\)。
- 起跳: 从 \(i\) 点起跳后,我们的位置瞬间变为 \((x_i, y_i + d)\)。
- 下落时间: 要想到达 \(j\),我们的高度需要从 \(y_i + d\) 下降到 \(y_j\)。因为每秒下降 1 格,所以这个过程需要的时间是 \(t = (y_i + d) - y_j\) 秒。
- 如果 \(y_j\) 比 \(y_i + d\) 还高,那么 \(t\) 就是负数,说明根本不可能通过下落到达,所以这种情况是不可能实现的。
- 水平移动: 在这 \(t\) 秒的下落时间里,我们每秒最多可以水平移动 1 格。所以,我们总共最多可以水平移动 \(t\) 格。
- 判定条件: 要想从 \(i\) 到达 \(j\),我们需要的水平移动距离是 \(|x_i - x_j|\)。只要我们拥有的最大水平移动能力不小于所需的距离,我们就能到达。
- 即:最大可移动距离 \(\ge\) 所需移动距离
- 代入公式:\((y_i + d) - y_j \ge |x_i - x_j|\)
只要这个不等式成立,就说明存在一条从 \(i\) 到 \(j\) 的路径,我们就在图中连一条边 i -> j
。
现在,原问题就转化为了:在一个有向图中,从起点节点 \(c\) 出发,找到一条路径,使得路径经过的节点数量最多。
3. 核心思路:处理环路 -> 缩点
我们已经有了一个有向图。但是,这个图中可能存在“环路”。例如,可以从 A 到 B,又能从 B 回到 A。
环路有什么特别之处?
题目说跳跃球可以无限重复使用。这意味着,如果 A 和 B 在一个环里,一旦我们到达了 A,我们就可以通过环路轻松到达 B;反之,到达了 B 也能到达 A。更进一步,如果一个小组(比如 A, B, C)内的任意两个点都可以互相到达,那么只要我们进入了这个小组,我们就可以把小组内所有点的得分(3分)全部拿到!
这个“内部可以互相到达的小组”在图论里有一个专门的名字,叫做 强连通分量 (Strongly Connected Component, SCC)。
我们的关键思路就是:把每个强连通分量看作一个整体。
- 缩点 (Node Contraction): 我们可以把一个强连通分量“压缩”成一个“超级节点”。这个超级节点的“得分”就是它内部包含的原始跳跃球的数量。
- 构建新图 (DAG): 经过缩点后,我们的图会变成一个没有环路的新图,也就是 有向无环图 (Directed Acyclic Graph, DAG)。因为如果新图中还有环,说明那几个超级节点本来就应该属于同一个更大的强连通分量。
- 新问题: 现在问题又被简化了:在一个有向无环图(DAG)中,从包含起点 \(c\) 的那个超级节点出发,找到一条路径,使得路径上所有超级节点的得分之和最大。
这个问题就变成了在 DAG 上求最长路径问题,这是一个非常经典的问题,可以用动态规划或者深度优先搜索(DFS)来解决。
4. 算法实现:一步步走向答案
结合上面的思路,我们的解题步骤如下:
-
第一步:建图
- 遍历所有可能的跳跃球对 \((i, j)\)。
- 根据我们推导的公式
y[i] - y[j] + d >= abs(x[i] - x[j])
,判断是否可以从 \(i\) 到达 \(j\)。如果可以,就添加一条有向边i -> j
。 - 同样,也判断是否可以从 \(j\) 到达 \(i\)。
-
第二步:缩点
- 使用 Tarjan 算法 或者 Kosaraju 算法来找出图中的所有强连通分量。Tarjan 算法是实现这个功能的常用标准算法。
- 在找到每个强连通分量后,我们给它一个唯一的编号(比如
Color
),并记录下这个分量里有多少个原始节点(作为这个超级节点的“权重”或“得分”,代码中用ww
数组记录)。
-
第三步:构建新图(DAG)
- 遍历原图中的每一条边
u -> v
。 - 如果
u
和v
不在同一个强连通分量里(即它们的color
不同),我们就在新图中,从u
所在的超级节点向v
所在的超级节点连一条边。
- 遍历原图中的每一条边
-
第四步:在 DAG 上求最长路
- 我们从起点
c
所在的那个超级节点color[c]
开始。 - 使用深度优先搜索 (DFS) + 记忆化来求解。定义
ans[p]
为从起点出发,到达超级节点p
时能获得的最大得分。 - DFS 函数
Dfs(p, current_score)
的逻辑是:- 当前在超级节点
p
,已获得的得分是current_score
。 - 更新
ans[p] = max(ans[p], current_score)
。 - 遍历
p
的所有邻居it
。 - 递归调用
Dfs(it, current_score + ww[it])
,去探索下一条路径。
- 当前在超级节点
- 为了避免重复计算和低效搜索,可以加入一个最优性剪枝:如果已有的
ans[it]
比我们即将通过新路径到达it
的得分current_score + ww[it]
还要高,那就没必要再走这条路了。
- 我们从起点
-
第五步:输出结果
- DFS 结束后,
ans
数组中记录了从起点到各个超级节点的最大得分。我们只需要遍历整个ans
数组,找到其中的最大值,就是本题的最终答案。
- DFS 结束后,
总结
这道题的解法是一个经典的组合拳:
物理问题 → 图论模型 → 缩点(SCC) → DAG最长路
通过这样一步步的转化,我们将一个看似复杂的动态模拟问题,变成了一个可以用标准算法高效解决的图论问题。提供的代码正是严格按照这个流程来实现的。