P5025 [SNOI2017] 炸弹 解题报告


P5025 [SNOI2017] 炸弹 解题报告

1. 题目解读:这道题到底要我们干什么?

想象一条很长的线上放着 n 个炸弹。每个炸弹 i 都有一个坐标 x_i 和一个爆炸半径 r_i

当一个炸弹(比如第 i 个)爆炸时,它会形成一个冲击波。任何处于 [x_i - r_i, x_i + r_i] 这个区间内的其他炸弹(比如第 j 个)都会被引爆。这种引爆是连锁的,j 爆炸后可能又会引爆其他炸弹。

我们的任务是: 对于每一个炸弹 i(从 1 到 n),计算如果手动引爆它,总共会引爆多少个炸弹(包括它自己)。最后,将每个炸弹 i 的引爆数量乘以它的编号 i,再把这些结果全部加起来,得到一个最终的总和。


2. 核心思路:从暴力到优化的思考过程

第一步:最直观的想法 —— 建模为图

这是一个典型的“连锁反应”问题,很自然地可以联想到图论

  • 点(Vertex):每个炸弹看作一个图中的点。
  • 边(Edge):如果炸弹 i 的爆炸范围能覆盖到炸弹 j,我们就从 ij 连一条有向边 i -> j。这表示 i 的爆炸可以直接导致 j 的爆炸。

这样,问题就转化成了:对于图中的每一个点 i,找出从它出发总共能到达多少个不同的点(即可达节点的数量)。

第二步:朴素解法及其瓶颈

根据上面的图模型,我们可以这样做:

  1. 建图:对于每个炸弹 i,遍历所有其他炸弹 j。如果满足 |x_j - x_i| <= r_i,就添加一条边 i -> j
  2. 统计:对于每个炸弹 i,从点 i 开始进行一次深度优先搜索(DFS)或广度优先搜索(BFS),统计所有能访问到的点的数量。

问题出在哪里?
数据范围 n 高达 500,000。

  • 建图:需要两层循环,时间复杂度是 O(n²),大约是 500000 * 500000,这太慢了。
  • 统计n 次搜索,每次的复杂度也可能很高。

所以,这个 O(n²) 的建图过程是主要瓶颈,必须优化。

第三步:优化的曙光 —— 区间连边

我们观察到,题面中有一个重要条件:x_i 是严格递增的。这意味着炸弹是按坐标排好序的。

因此,当炸弹 i 爆炸时,它能引爆的炸弹 j 的坐标范围是 [x_i - r_i, x_i + r_i]。由于所有炸弹的坐标 x 是有序的,那么这些被引爆的炸弹的编号也必然在一个连续的区间 [L, R] 内。

  • 我们可以用二分查找(lower_boundupper_bound)快速地找到这个编号区间 [L, R]

现在,建图的问题从“点 i一堆零散的点 j 连边”变成了“点 i一整个区间 [L, R] 内的所有点连边”。

虽然问题形式变了,但如果还是老实地把 iL, L+1, ..., R 里的每个点都连一条边,复杂度依然是 O(n²)。我们需要一种更高效的方式来处理“点向区间连边”。

第四步:神器登场 —— 线段树优化建图

这就是线段树大显身手的地方了。我们可以把点向区间的连边,变成点向少数几个代表这个区间的“大节点”连边。

  1. 建立一棵线段树:这棵线段树的叶子节点代表每个具体的炸弹(编号 1 到 n)。每个非叶子节点则代表一个编号区间。为了保证图的连通性,我们还需要从每个父节点向它的两个子节点连边。这代表着,如果一个炸弹能引爆区间 [l, r],它自然也能引爆其中的子区间 [l, mid][mid+1, r]

    线段树结构
    (上图很好地展示了这个思想,最下面一排是原始的点,上面是代表区间的节点)

  2. 执行连边:当我们需要从炸弹 i 向区间 [L, R] 连边时,我们在线段树上找到能完整覆盖 [L, R]log(n) 级别的节点,然后从 i 对应的叶子节点向这些“区间代表节点”连边。

通过这种方式,我们把总的边数从 O(n²) 级别成功地降低到了 O(n log n) 级别,时空复杂度都得到了极大的改善。


3. 解决深层问题:相互引爆与强连通分量

建好了图,我们还需要解决一个问题:如果炸弹 A 能引爆 B,同时 B 也能引爆 A,它们就形成了一个“小团体”。任何能引爆 A 的炸弹,也就能引爆这个团体里的所有成员。

这种“小团体”在图论中被称为强连通分量(Strongly Connected Component, SCC)。在同一个 SCC 里的所有点都是相互可达的。

  1. Tarjan 算法与缩点:我们可以使用经典的 Tarjan 算法来找出图中所有的 SCC。然后,我们将每个 SCC “压缩”成一个新的超级点。这个过程叫做缩点
  2. 构建新图(DAG):缩点之后,原来的复杂图就变成了一个更简单的有向无环图(DAG)。新图中的边,就是原来两个不同 SCC 之间的连接。每个超级点的“权值”就是它内部包含的原始炸弹的数量。

4. 在新图上计算最终答案

现在问题变得清晰了:在一个 DAG 上,对于每个(包含原始炸弹 i 的)超级点,计算它能到达的所有超级点的总权值之和。

  1. 在 DAG 上进行 DFS:由于是 DAG,我们可以通过一次 DFS 来更新每个超级点能到达的“势力范围”。在题解代码中,这是通过维护每个超级点最终能引爆的炸弹编号的最小和最大值 left[scc]right[scc] 来实现的。
  2. 更新范围:对于一个超级点 u,它最终能到达的范围,是它自己本身的范围,以及它能直接到达的所有下游超级点 v 的范围的并集。我们在 DAG 上进行一次反向拓扑序的更新(或者说,一次 DFS 遍历)即可完成这个计算。
  3. 统计答案:经过最后的 DFS,我们得到了每个超级点最终能引爆的炸弹编号区间 [left, right]。对于原始的每个炸弹 i,它所在的超级点能引爆的炸弹总数就是 right - left + 1
  4. 计算总和:最后,遍历炸弹 1 到 n,将 i * (引爆数量) 累加到最终答案 ans 中,并按要求取模。

总结

本题的解法是一个组合拳,环环相扣:

  1. 图论建模:将问题转化为图的连通性问题。
  2. 线段树优化建图:利用坐标有序的特性,将 O(n²) 的点对点连边优化为 O(n log n) 的点对区间连边,解决了核心瓶颈。
  3. Tarjan 缩点:处理图中复杂的环(相互引爆),将图简化为 DAG。
  4. DAG 上的 DP/DFS:在新图上高效地统计每个点最终的“战果”。

通过这一系列步骤,我们将一个看似棘手的难题分解成了几个经典的算法模块,最终在要求的时间复杂度内解决了问题。

posted @ 2025-07-13 22:52  surprise_ying  阅读(36)  评论(0)    收藏  举报