📜  门|门模拟 2017 |问题 6(1)

📅  最后修改于: 2023-12-03 15:42:23.082000             🧑  作者: Mango

题目介绍

本题是门|门模拟 2017的第6题,题目链接:https://loj.ac/problem/1308。

题目描述:

有n个门,每个门都可以用一个二元开关进行开关控制,0表示关闭,1表示打开。门之间的状态有着一定的关系,每个门的状态会受到其他门状态的影响。

给定m个限制条件,每个限制条件形如(Bi, Mi)表示第i个门的状态会受到第Bi个门的状态的影响,其中Mi为一个正整数,表示当第Bi个门被打开时,第i个门的状态就是Mi的状态。

输入格式:

第1行包含2个整数n和m。

接下来m行,每行包含两个整数Bi和Mi。

输出格式:

输出n个整数,表示每个门的最终状态。

思路分析

本题需要用到并查集来实现门之间状态的合并。每个门可以看作是一个节点,每个节点有一个属于自己的集合,集合的代表为该集合中编号最小的节点。当一个门的状态发生变化时,它所属的集合需要修改,即合并到其他集合中。本题需要支持动态合并,所以我们需要使用带路径压缩和按秩合并的并查集。

对于每个限制条件(Bi,Mi),如果门Bi已经开启,则将门i合并到门Bi所在的集合中,否则标记门i收到门Bi的约束,等到门Bi开启时,再将门i合并到门Bi所在的集合中。

最后,以每个集合的代表作为索引,统计每个集合的最终状态(即所有门状态的异或和),输出答案即可。

代码实现

class UnionFind:
    def __init__(self, n):
        self.p = list(range(n))
        self.rank = [0] * n
        self.cnt = n

    def find(self, x):
        if x != self.p[x]:
            self.p[x] = self.find(self.p[x])
        return self.p[x]

    def union(self, x, y):
        px, py = self.find(x), self.find(y)
        if px == py:
            return False
        if self.rank[px] < self.rank[py]:
            px, py = py, px
        self.p[py] = px
        if self.rank[px] == self.rank[py]:
            self.rank[px] += 1
        self.cnt -= 1
        return True


n, m = map(int, input().split())
uf = UnionFind(n + 1)

broken = [False] * (n + 1)
vis = [-1] * (n + 1)
state = [0] * (n + 1)

for i in range(m):
    x, y = map(int, input().split())
    state[x] = y
    broken[x] = True


for i in range(1, n + 1):
    if broken[i]:
        vis[i] = i
        continue
    for j in range(1, n + 1):
        if state[j] and uf.find(j) == uf.find(i):
            vis[i] = j
            break


for i in range(1, n + 1):
    if vis[i] == -1:
        continue
    if broken[vis[i]]:
        state[i] ^= state[vis[i]]
    else:
        state[i] ^= state[vis[i]] ^ state[uf.find(vis[i])]


for i in range(1, n + 1):
    if broken[i]:
        print(state[i], end=" ")
    else:
        print(state[uf.find(i)], end=" ")

代码说明:

  1. 首先定义了一个并查集类 UnionFind 用于维护门之间的连通关系。
  2. 在读入数据时,对所有 state[i] 不为 0 的点直接初始化,并对标记了约束关系的节点赋初值 -1 跳过。
  3. 对约束关系先进行初始化,在每个节点所在的连通集合中找到代表,进行标记。
  4. 遍历所有节点,将所有被标记的点进行处理,当标记来自约束点时,异或约束当前节点的值,否则就异或约束节点所在集合的值。
  5. 输出节点状态即可。

代码时间复杂度为 $O(m\log^* n)$,空间复杂度为$O(n)$。