P9431 [NAPC-#1] Stage3 - Jump Refreshers 解题报告


P9431 [NAPC-#1] Stage3 - Jump Refreshers 解题报告

1. 读懂题目:我们要解决什么问题?

想象一下,你是一个游戏高手 "kid",在一个二维世界里玩跳跃游戏。这个世界里有 \(n\) 个“跳跃球”。

游戏规则是这样的:

  1. 起点: 你从第 \(c\) 个跳跃球开始。
  2. 跳跃: 当你站在一个跳跃球上时,你可以选择“跳一下”。这一跳会让你瞬间垂直向上移动 \(d\) 个单位。
  3. 下落与移动: 跳起来后,你就处于空中。接下来,每一秒钟会发生两件事:
    • 你会自动往下掉 1 个单位。
    • 你可以选择向左移动 1 格、向右移动 1 格,或者原地不动。
  4. 再次跳跃: 只有当你再次精确地落在一个跳跃球上时,你才能进行下一次跳跃。
  5. 目标: 你的“得分”是你从多少个不同的跳跃球上成功起跳过。你的目标就是让这个得分最大化。
  6. 重要提示: 跳跃球可以无限次使用。

简单来说,我们要找一条从起点开始的路径,这条路径能让我们在尽可能多的不同跳跃球上进行跳跃。

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)\)

  1. 起跳:\(i\) 点起跳后,我们的位置瞬间变为 \((x_i, y_i + d)\)
  2. 下落时间: 要想到达 \(j\),我们的高度需要从 \(y_i + d\) 下降到 \(y_j\)。因为每秒下降 1 格,所以这个过程需要的时间是 \(t = (y_i + d) - y_j\) 秒。
    • 如果 \(y_j\)\(y_i + d\) 还高,那么 \(t\) 就是负数,说明根本不可能通过下落到达,所以这种情况是不可能实现的。
  3. 水平移动: 在这 \(t\) 秒的下落时间里,我们每秒最多可以水平移动 1 格。所以,我们总共最多可以水平移动 \(t\) 格。
  4. 判定条件: 要想从 \(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)

我们的关键思路就是:把每个强连通分量看作一个整体

  1. 缩点 (Node Contraction): 我们可以把一个强连通分量“压缩”成一个“超级节点”。这个超级节点的“得分”就是它内部包含的原始跳跃球的数量。
  2. 构建新图 (DAG): 经过缩点后,我们的图会变成一个没有环路的新图,也就是 有向无环图 (Directed Acyclic Graph, DAG)。因为如果新图中还有环,说明那几个超级节点本来就应该属于同一个更大的强连通分量。
  3. 新问题: 现在问题又被简化了:在一个有向无环图(DAG)中,从包含起点 \(c\) 的那个超级节点出发,找到一条路径,使得路径上所有超级节点的得分之和最大。

这个问题就变成了在 DAG 上求最长路径问题,这是一个非常经典的问题,可以用动态规划或者深度优先搜索(DFS)来解决。

4. 算法实现:一步步走向答案

结合上面的思路,我们的解题步骤如下:

  1. 第一步:建图

    • 遍历所有可能的跳跃球对 \((i, j)\)
    • 根据我们推导的公式 y[i] - y[j] + d >= abs(x[i] - x[j]),判断是否可以从 \(i\) 到达 \(j\)。如果可以,就添加一条有向边 i -> j
    • 同样,也判断是否可以从 \(j\) 到达 \(i\)
  2. 第二步:缩点

    • 使用 Tarjan 算法 或者 Kosaraju 算法来找出图中的所有强连通分量。Tarjan 算法是实现这个功能的常用标准算法。
    • 在找到每个强连通分量后,我们给它一个唯一的编号(比如 Color),并记录下这个分量里有多少个原始节点(作为这个超级节点的“权重”或“得分”,代码中用 ww 数组记录)。
  3. 第三步:构建新图(DAG)

    • 遍历原图中的每一条边 u -> v
    • 如果 uv 不在同一个强连通分量里(即它们的 color 不同),我们就在新图中,从 u 所在的超级节点向 v 所在的超级节点连一条边。
  4. 第四步:在 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] 还要高,那就没必要再走这条路了。
  5. 第五步:输出结果

    • DFS 结束后,ans 数组中记录了从起点到各个超级节点的最大得分。我们只需要遍历整个 ans 数组,找到其中的最大值,就是本题的最终答案。

总结

这道题的解法是一个经典的组合拳:

物理问题 → 图论模型 → 缩点(SCC) → DAG最长路

通过这样一步步的转化,我们将一个看似复杂的动态模拟问题,变成了一个可以用标准算法高效解决的图论问题。提供的代码正是严格按照这个流程来实现的。

posted @ 2025-07-14 14:33  surprise_ying  阅读(15)  评论(0)    收藏  举报