1 条题解
-
0
B. 矩阵稳定化 详细题解
题目重述
给定一个 的整数矩阵 。反复执行以下操作:
- 找到任意一个单元格 ,满足 严格大于它所有上、下、左、右相邻单元格的值(忽略边界)。如果有多个,选择行号最小的,再选列号最小的。
- 将该单元格的值减少 。 直到不存在这样的单元格。输出最终矩阵。保证算法在有限步内终止。
核心观察
- 操作只减少值,不会增加,因此每个单元格的值单调递减。
- 当某个单元格的值大于所有邻居时,它会被反复减 ,直到它不再大于所有邻居。
- 设单元格 的邻居中最大值为 ,则 最终的值必定等于 (因为当它大于 时会一直减到 ,一旦等于 就不再满足“严格大于”条件)。但注意, 本身也可能随着邻居的减少而变化,因此最终值可能小于初始 。
- 整个稳定过程等价于:反复将当前所有局部最大值(严格大于所有邻居)降低到其邻居的最大值,直到没有局部最大值。
算法思想
由于每次只将最大的局部峰值降低到邻居的最大值,我们可以使用优先队列(最大堆)来模拟。但直接模拟逐次减 会超时(值可达 ),因此我们一次性地将单元格的值设为邻居的最大值(而不是每次减 )。这样每个单元格最多被处理一次(原因见后),总复杂度 。
具体步骤:
- 读取矩阵 ,初始化一个最大堆(优先队列),元素为
(a[i][j], i, j),按值降序、行升序、列升序排列(但堆默认按值最大,我们直接存值即可,因为处理顺序并不严格依赖行列,最终结果唯一)。 - 复制一份当前矩阵
cur,用于记录实时值。 - 当堆非空:
- 弹出堆顶
(val, i, j)。 - 如果
cur[i][j] != val,说明该单元格已经被更新过,跳过。 - 计算四个方向上邻居的最大值
mx(忽略边界)。 - 如果
val > mx,则将该单元格的值更新为mx(即cur[i][j] = mx),并将(mx, i, j)重新入堆。 - 否则,不做任何操作。
- 弹出堆顶
- 最终
cur即为稳定后的矩阵。
正确性证明
引理 1:每个单元格的值最多被更新一次。
证明:假设单元格 第一次被处理时,其值 ,于是被更新为 。此后, 的值等于 。因为 是当时其邻居的最大值,所以至少存在一个邻居 满足 。之后任何其他单元格的更新只会使值减小,因此 只会变小或不变,而 保持 不变。那么 ,且当 严格变小时,,但 可能还有其它邻居,其中至少有一个(即 )的值小于 。为了 再次成为局部最大值,需要 严格大于所有邻居。然而, 的邻居中包括 ,而 且 ,当 时, 确实大于 ,但 可能还存在另一个邻居 最初等于 ,若 后来也被降低,那么 就可能大于所有邻居。但注意到 与 相邻,且 时, 本身也是一个局部最大值(如果它的其他邻居都不大于 ),那么 也会被处理并降低。然而,当 被处理后,其值也会变成它邻居的最大值,但该最大值一定不超过 ,且由于 的值保持 , 的新值 。此时 的邻居中 的值变为 , 的值也 ,但 仍然大于等于它们,然而 还有一个邻居?实际上, 可能不止两个邻居,但关键是 自身并没有被再次入堆,因为它的值从未改变过。在算法中,只有当单元格的值被修改时,我们才重新入堆。 的值在第一次更新后就固定了,后面不会再被修改,因此它不可能再次被处理。所以每个单元格只会被更新一次,从而每个单元格最多入堆两次(初始一次,更新后一次)。因此总操作次数为 。引理 2:算法结束时,矩阵中没有任何单元格的值严格大于所有邻居。
证明:若存在这样的单元格,它一定会被堆处理。因为堆中始终包含所有大于邻居最大值的单元格(初始全部入堆,每次更新后新值也会入堆)。当堆为空时,意味着没有单元格满足条件。引理 3:最终矩阵与按题目描述的逐次减 操作得到的结果相同。
证明:逐次减 的过程相当于将每个局部最大峰值逐步降低,而我们的算法一次降低到邻居最大值,这等价于跳过了中间不必要的减 步骤。由于每次降低的目标值相同(均为当时邻居的最大值),且后续操作不依赖中间值,因此最终结果一致。复杂度分析
- 每个单元格初始入堆一次,更新后至多再入堆一次,总入堆次数 。
- 每次堆操作 ,总时间 。
- 空间 。
- 所有测试用例的 之和 ,完全可行。
实现细节
- 使用
priority_queue<tuple<int,int,int>>,默认按第一个元素降序,与需求一致。 - 需要存储当前矩阵
cur以便比较和更新。 - 注意边界处理:邻居只考虑存在的位置,最大值初始化为 或很小的数(因为所有值 )。
示例验证
以第一个测试用例
1 2,矩阵[3, 1]为例:- 初始堆:
(3,0,0),(1,0,1)。 - 弹出
(3,0,0),邻居只有(0,1)值为 ,最大值 ,,故将cur[0][0]设为 ,入堆(1,0,0)。 - 此时堆中有
(1,0,0),(1,0,1)。 - 弹出
(1,0,0),检查邻居最大值(右边 ), 不大于 ,跳过。 - 弹出
(1,0,1),邻居只有左边 , 不大于 ,跳过。 - 堆空,结束。得到
[1,1],符合输出。
其他测试用例类似。
总结
本题的核心是将逐次减 的复杂过程转化为一次直接降低到邻居最大值,并利用优先队列模拟“从大到小”的稳定化过程。每个单元格最多更新一次,保证了高效性。最终矩阵是唯一的,且算法易于实现。
- 1
信息
- ID
- 6986
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者