二维差分算法高效解靶场问题
问题分析
给定一个 N×MN \times MN×M 的 01 矩阵(表示每个点是否被子弹中心击中)和子弹尺寸参数 l,rl, rl,r(子弹实际大小为 (2l+1)×(2r+1)(2l+1) \times (2r+1)(2l+1)×(2r+1)),需要输出最终靶子的状态矩阵(1 表示被摧毁,0 表示完好)。
关键规则
- 子弹中心被击中(输入矩阵中为 1)会摧毁以其为中心的矩形区域
- 摧毁范围:以中心点 (i,j)(i,j)(i,j) 为基准:
- 行范围:[max(0,i−r),min(N−1,i+r)][\max(0, i-r), \min(N-1, i+r)][max(0,i−r),min(N−1,i+r)]
- 列范围:[max(0,j−l),min(M−1,j+l)][\max(0, j-l), \min(M-1, j+l)][max(0,j−l),min(M−1,j+l)]
- 多个子弹区域可能重叠
高效算法:二维差分数组
使用二维差分数组可在 O(NM)O(NM)O(NM) 时间复杂度内高效标记所有被摧毁区域,优于暴力方法的 O(NM⋅(2l+1)(2r+1))O(NM \cdot (2l+1)(2r+1))O(NM⋅(2l+1)(2r+1))。
算法步骤
-
初始化差分数组
- 创建大小为 (N+2)×(M+2)(N+2) \times (M+2)(N+2)×(M+2) 的二维数组
diff(多出的空间用于边界处理) - 初始化为 0
- 创建大小为 (N+2)×(M+2)(N+2) \times (M+2)(N+2)×(M+2) 的二维数组
-
标记摧毁区域
- 遍历输入矩阵的每个点 (i,j)(i,j)(i,j):
if grid[i][j] == '1':row_start = max(0, i - r)row_end = min(N-1, i + r)col_start = max(0, j - l)col_end = min(M-1, j + l)# 差分标记diff[row_start][col_start] += 1diff[row_start][col_end+1] -= 1diff[row_end+1][col_start] -= 1diff[row_end+1][col_end+1] += 1
- 遍历输入矩阵的每个点 (i,j)(i,j)(i,j):
-
计算前缀和得到覆盖状态
- 创建结果数组
res(大小 N×MN \times MN×M) - 计算二维前缀和:
for i in range(N):for j in range(M):# 前缀和公式res[i][j] = diff[i][j]if i > 0:res[i][j] += res[i-1][j]if j > 0:res[i][j] += res[i][j-1]if i > 0 and j > 0:res[i][j] -= res[i-1][j-1]# 转换为 0/1 状态res[i][j] = '1' if res[i][j] > 0 else '0'
- 创建结果数组
-
输出结果
- 将
res按行输出为字符串
- 将
示例演示
输入:
2 2 1 1 # N=2, M=2, l=1, r=1
00
01 # 中心点 (1,1) 被击中
计算过程:
- 子弹中心 (1,1) 的摧毁范围:[0,1]×[0,1][0,1] \times [0,1][0,1]×[0,1](整个矩阵)
- 差分标记:
diff[0][0] += 1 → (0,0): +1 diff[0][2] -= 1 → (0,2): -1(边界外) diff[2][0] -= 1 → (2,0): -1(边界外) diff[2][2] += 1 → (2,2): +1(边界外) - 前缀和计算:
坐标 计算过程 值 状态 (0,0) 1 + 0 + 0 - 0 1 ‘1’ (0,1) 0 + 1 + 0 - 0 1 ‘1’ (1,0) 0 + 1 + 0 - 0 1 ‘1’ (1,1) 0 + 1 + 1 - 1 1 ‘1’
输出:
11
11
完整代码实现
def main():import sysdata = sys.stdin.read().splitlines()if not data: return# 解析第一行N, M, l, r = map(int, data[0].split())grid = data[1:1+N]# 初始化差分数组 (N+2) x (M+2)diff = [[0] * (M+2) for _ in range(N+2)]# 处理每个子弹中心for i in range(N):for j in range(M):if grid[i][j] == '1':row_start = max(0, i - r)row_end = min(N-1, i + r)col_start = max(0, j - l)col_end = min(M-1, j + l)# 差分标记diff[row_start][col_start] += 1diff[row_start][col_end+1] -= 1diff[row_end+1][col_start] -= 1diff[row_end+1][col_end+1] += 1# 计算二维前缀和res = [['0'] * M for _ in range(N)]for i in range(N):for j in range(M):# 继承上方和左侧的值if i > 0:diff[i][j] += diff[i-1][j]if j > 0:diff[i][j] += diff[i][j-1]if i > 0 and j > 0:diff[i][j] -= diff[i-1][j-1]# 转换为摧毁状态res[i][j] = '1' if diff[i][j] > 0 else '0'# 输出结果for row in res:print(''.join(row))if __name__ == "__main__":main()
算法复杂度
- 时间复杂度:O(NM)O(NM)O(NM)
- 遍历输入矩阵:O(NM)O(NM)O(NM)
- 差分标记操作:每个子弹中心 O(1)O(1)O(1)
- 前缀和计算:O(NM)O(NM)O(NM)
- 空间复杂度:O(NM)O(NM)O(NM)
- 差分数组:(N+2)×(M+2)(N+2) \times (M+2)(N+2)×(M+2)
- 结果数组:N×MN \times MN×M
边界处理
- 矩阵边界:
- 使用
max(0, ...)和min(N-1/M-1, ...)确保坐标不越界
- 使用
- 差分数组:
- 额外增加 2 行/列防止边界溢出
- 重叠区域:
- 差分累加自动处理多次覆盖
此算法能高效处理 103×10310^3 \times 10^3103×103 规模的数据,满足绝大多数应用场景需求。
