📅  最后修改于: 2023-12-03 15:28:47.495000             🧑  作者: Mango
这是一道来自于 Sudo GATE 2021 的测试题,题目编号为 16,名称为“门”。
有一个 2n x 2n 的方阵,由若干个相邻的格子组成,其中有些格子是关闭的而有些格子是打开的。现在,你需要写一个程序实现以下操作:
为了方便处理,我们将这个方阵分成四个等分,编号分别为 1、2、3、4。每个等分又可以进一步分成四个等分,依次编号为 1、2、3、4。例如,原始的方阵如下所示:
1|1 2|2
-+-+--
3|3 4|4
-+-+-+-
5|6 7|8
-+-+--
9|10|11
则四个等分分别为:
1|1 2
-+-+-
3|4 5
6|7 8
-+-+-
9|10|11
其中,等分 1 下面的两个等分中的格子行标和列标都是等分原来所在的格子的两倍。例如,等分 1 中的格子 (2, 3) 在等分 3 中的坐标为 (4, 6),在等分 2 中的坐标为 (2, 6)。其余等分中格子坐标的变换也类似。
以下是一份 Python 代码实现,用于实现上述操作:
class Node:
def __init__(self):
self.value = 0
self.op = 0
class SegmentTree:
def __init__(self, n):
self.tree = [Node() for i in range(n * 4)]
def push_up(self, p):
self.tree[p].value = (self.tree[p * 2].value |
self.tree[p * 2 + 1].value)
def push_down(self, p):
if not self.tree[p].op:
return
self.tree[p * 2].value ^= 1
self.tree[p * 2 + 1].value ^= 1
self.tree[p * 2].op ^= 1
self.tree[p * 2 + 1].op ^= 1
self.tree[p].op = 0
def build(self, p, l, r, a):
if l == r:
self.tree[p].value = a[l]
else:
mid = (l + r) // 2
self.build(p * 2, l, mid, a)
self.build(p * 2 + 1, mid + 1, r, a)
self.push_up(p)
def query(self, p, l, r, x, y):
if x <= l and r <= y:
return self.tree[p].value
else:
self.push_down(p)
mid = (l + r) // 2
res = 0
if x <= mid:
res |= self.query(p * 2, l, mid, x, y)
if mid < y:
res |= self.query(p * 2 + 1, mid + 1, r, x, y)
return res
def update(self, p, l, r, x, y):
if x <= l and r <= y:
self.tree[p].value ^= 1
self.tree[p].op ^= 1
else:
self.push_down(p)
mid = (l + r) // 2
if x <= mid:
self.update(p * 2, l, mid, x, y)
if mid < y:
self.update(p * 2 + 1, mid + 1, r, x, y)
self.push_up(p)
本题的核心是使用线段树来维护格子的状态。对于每个 等分,我们都对应一颗线段树来维护其中包含的格子状态。例如,我们可以为等分 1 维护一颗包含 2n * 2n 个叶子节点的线段树。由于一个格子的状态只跟其所在的等分和坐标有关,因此线段树的每个节点只需要维护一个二进制数即可,代表其对应部分区域内所有格子的状态的“或”。
查询某个位置的状态可以通过递归地向下寻找线段树的叶子节点来实现,时间复杂度为 O(log2n)。
翻转某个区域内的所有格子状态可以采用递归的方法实现。具体而言,假设我们要翻转从 (x1, y1) 到 (x2, y2) 的区域,对于每个节点对应区间与翻转区间的交集不为空的线段树节点,我们需要先递归地将其子节点的状态全部翻转(即将节点的 op 域取反,使其 push_down 时完成状态翻转),然后再将本节点的状态翻转。时间复杂度同样为 O(log2n)。