B4309 [蓝桥杯青少年组国赛 2024] 第四题 解题报告
B4309 [蓝桥杯青少年组国赛 2024] 第四题 解题报告
题目核心思想
这道题的目标是:给你一个棋盘,上面有一些棋子。你需要移除最少数量的棋子,使得剩下的棋子分裂成两个或更多个独立的“连通块”。如果无论如何都做不到,就输出 -1。
一个“连通块”指的是一群棋子,你可以从其中任何一个棋子出发,只通过水平或竖直的相邻移动,到达这群棋子里的任何其他棋子。
解题步骤详解
为了解决这个问题,我们可以把复杂的棋盘问题,转换成一个我们更熟悉的图论问题。
第一步:从棋盘到图的转换
想象一下,每个棋子都是图中的一个节点(或顶点)。如果两个棋子在棋盘上是水平或竖直相邻的,我们就在它们对应的两个节点之间连一条边。
这样,整个棋盘上的棋子布局就变成了一张图。题目中的“连通块”就对应着图论中的“连通分量”。
我们的新目标就变成了:移除最少的节点,使得图的连通分量数量大于等于 2。
为了方便在代码中处理,我们给每个网格一个独一无二的编号。例如,一个 \(n\) 行 \(m\) 列的棋盘,我们可以把第 i
行第 j
列的格子编号为 (i-1) * m + j
。这样,每个棋子(节点)都有了一个唯一的“名字”。
第二步:分类讨论,找到答案
现在我们有了一张代表棋子关系的图,问题就清晰多了。我们可以根据图的初始状态,分几种情况来讨论:
情况一:天上掉馅饼,一开始就满足要求
- 判断条件:初始的图就已经有 2 个或更多的连通分量。
- 解释:这意味着棋子本来就分成了好几堆,互相不挨着。我们不需要移除任何棋子,就已经满足了“出现两个及以上连通块”的要求。
- 答案:0
- 代码实现:在建图后,我们可以用深度优先搜索(DFS)或广度优先搜索(BFS)来遍历图,统计一下连通分量的数量。如果数量大于1,直接输出0。
情况二:巧妇难为无米之炊,根本无法分割
- 判断条件:棋盘上棋子的总数少于 3 个(也就是 0、1 或 2 个)。
- 解释:要想分成两个连通块,每个连通块至少需要 1 个棋子,也就是说,我们最少需要剩下 2 个棋子。如果总共都不到 3 个棋子,就算我们移除了 1 个,剩下的棋子也不够分成两堆。所以,这种情况是无解的。
- 答案:-1
- 代码实现:在读入数据时,顺便统计一下棋子(
G
)的总数。如果总数小于3,直接输出-1。
情况三:找到关键先生,只移除一个就搞定
- 判断条件:图是连通的(即只有一个连通块),并且图中存在“割点”。
- 解释:什么是“割点”?在一个连通的图中,如果移除了某个节点(以及与它相连的所有边)后,这个图会分裂成两个或更多的连通分量,那么这个被移除的节点就叫做“割点”。它就像一个交通枢纽,一旦被破坏,整个交通网络就瘫痪成几块了。
所以,如果我们能找到一个割点,那么只需要移除它所代表的那颗棋子,整个棋子群就会被分开。 - 答案:1
- 代码实现:寻找割点有经典的算法,叫做 Tarjan 算法。代码中的
dfs
函数就是一个 Tarjan 算法的实现,它通过计算每个节点的dfn
(访问时间戳)和low
(能追溯到的最早祖先的时间戳)来判断一个点是否是割点,并将结果记录在iscut
数组里。我们只需要在遍历完图后,检查是否存在iscut[i]
为true
的节点即可。
情况四:铁板一块,必须下狠手
- 判断条件:以上情况都不满足。也就是说,图是连通的,棋子数量大于等于3,但图中没有割点。
- 解释:一个没有割点的连通图,意味着它非常“结实”。你拿掉任何一个节点,剩下的部分依然是连通的。这就像一个环路,你去掉任何一个点,剩下的点还是能互相到达。
在这种情况下,只移除 1 个棋子是无法分割棋子群的。我们必须移除至少 2 个。
幸运的是,对于这种由格子组成的图,移除 2 个棋子总是能成功。我们可以想象一个最简单的“结实”结构,比如一个2x2
的棋子方块。我们只要移除其中一个角上的棋子相邻的两个棋子,这个角上的棋子就被孤立出来了。 - 答案:2
- 代码实现:如果程序走到了这一步,说明它排除了情况一、二、三。那么根据逻辑,答案必然是 2。
代码逻辑梳理
提供的 C++ 代码完美地实现了上述思路:
-
main
函数:- 用一个
while(T--)
循环处理多组数据。 init()
:每次循环开始时,清空上次计算用的数组和图,避免数据干扰。- 读入和建图:
- 读取
n
和m
,然后用一个二重循环读入棋盘字符。 id[i][j]=((i-1)*m+j)
给每个格子编号。ans2++
统计棋子总数。- 第二个二重循环调用
add
函数,根据相邻关系建立图的邻接表G
。
- 读取
- 分析图:
ans
变量用来统计初始连通块数量。通过if(dfn[id[i][j]]==0 ...)
这个判断,确保对每个连通块只进行一次 DFS。dfs(id[i][j],-1)
:对每个连通块的起始点调用 Tarjan 算法,寻找割点。cnt
变量统计找到的割点总数。
- 输出答案:
if(ans > 1)
对应 情况一。else if(ans2 < 3)
对应 情况二。else if(cnt > 0)
对应 情况三。else
对应 情况四。
- 用一个
-
dfs
函数:- 这是 Tarjan 算法的核心,用于寻找割点。它通过深度优先搜索,巧妙地利用
dfn
和low
数组判断每个节点是否为割点。如果你对图论算法感兴趣,可以深入学习这个算法,但对于理解本题的解法,只需要知道它的作用是“找出所有的割点”即可。
- 这是 Tarjan 算法的核心,用于寻找割点。它通过深度优先搜索,巧妙地利用
总结
本题是一个非常典型的将实际问题转化为图论模型的例子。解题的关键在于能够想到用图来表示棋子间的关系,并根据图的性质(连通性、是否存在割点)进行分类讨论,最终得到一个清晰、完整的解题策略。