📜  门|门CS 2010 |第 58 题(1)

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

题目描述

题目链接:门|门CS 2010 |第 58 题

有一排门,每个门可以是“开”或“关”。从左到右数第i个门和第i+1个门可以用一个开关来控制它们的状态,如果状态为“关”,则互相之间会有一扇墙。

现在有n个人要进入这一排门中的一些门(一个人只能从门的一侧进入)。由于人太多,他们决定两个人之间必须隔一个门进入,即如果第i个人和第i+1个人要进入同一个门则他们必须从不同的一侧进入,具体来说,如果第i个人要进入第k个门(仅当第k个门是开着的)第i+1个人只能进入第k+2个门。

现在给出m个限制,限制有两种形式:

  1. 第i个人和第j个人必须隔k个门进入;(x[1] < x[2])

  2. 第i个人和第j个人必须隔k个门进入,并且之间必须有一扇门开着;

请问这n个人能否进入这n个门?如果能,输出一种方案。

输入格式

第一行两个整数m,n。

接下来m行,每行描述一个限制,格式为x[1] x[2] k t,表示两个人之间必须隔k个门进入,如果t=1,则之间必须有一扇门开着。

输出格式

如果无解,则输出-1。

否则按顺序输出每个人从哪个门进入,每个数占一行,第i个数表示第i个人从哪个门进入。

数据范围

1≤m≤105,

1≤n,k≤100,

1≤x[1],x[2]≤n,

t=0或1.

解题思路

这是一道十分经典的建图问题,我们可以将其转换为图模型,并通过染色方法解决。

首先想到的是用一个二分图表示门和人的位置,但是由于门之间存在关联,二分图并不能满足限制,我们需要将人和门分开处理。

由于两个人之间必须隔k个门进入,我们考虑将门看做节点, 如果两个人之间必须隔k个门进入,则他们之间的所有门节点之间没有边连在一起。如果两个人之间必须隔k个门进入,并且之间必须有一扇门开着,则这两个人之间的所有门节点之间没有边连在一起,除了其中一扇门的两个节点之间存在一条有向边。

我们将这些门节点作为左部的节点,人作为右部的节点,如果一个人可以从某个门进入,则从该人节点向它可以进入的门节点连有向边。需要注意的是,由于人之间必须隔一个门进入,因此我们需要将同侧不可进入的门节点看做一组,将其用一个虚拟节点代表,然后将该虚拟节点与其他门节点连边。最后再加上源和汇节点,并将源节点与所有人节点连边,将所有可进入的门节点与汇节点连边。最后我们通过染色方法来求出图的一个可行解。

该题代码实现比较复杂,需要使用邻接表存储建图,同时求解的过程中需要涉及到虚拟节点的映射,编写代码时需要注意细节问题。

代码实现

# 门|门CS 2010 |第 58 题
# 使用邻接表存储建图,代码实现比较复杂,需要注意细节问题。

class Solution:
    def __init__(self):
        self.INF = 0x3f3f3f3f
        self.color = []
        self.head = []
        self.edge = []

    def dfs(self, u):
        self.color[u] = 1
        for i in range(self.head[u], -1, -1):
            v, w = self.edge[i]
            if self.color[v] == 1:
                return False
            if self.color[v] == 0 and not self.dfs(v):
                return False
        self.color[u] = 2
        return True

    def addEdge(self, u, v, w):
        self.edge.append((v, w, self.head[u]))
        self.head[u] = len(self.edge) - 1

    def topoSort(self, n):
        self.color = [0] * (n + 1)
        for i in range(n):
            if self.color[i] == 0 and not self.dfs(i):
                return False
        return True

    def solve(self, n, m, edgeList):
        # 构建图
        self.edge.clear()
        self.head = [-1] * (n * 3 + 2)
        s, t = n * 3, n * 3 + 1
        for i in range(n):
            self.addEdge(s, i, 1)
            self.addEdge(i + n * 2, t, 1)

        # 人分组
        for i in range(n):
            self.addEdge(i, i + n, 1)
            self.addEdge(i + n, i, 0)

        # 门分组
        for i in range(1, n + 1):
            self.addEdge(i + n, i + n * 2, 1)
            self.addEdge(i + n * 2, i + n, 0)

        # 添加关系图
        for i in range(m):
            a, b, k, flg = edgeList[i]
            x, y = 2 * a - 1, 2 * b - 1
            if flg:
                for j in range(max(1, k - n), min(k + 1, n)):
                    if j != a and j != b:
                        self.addEdge(j + n * 2, x + 1, self.INF)
                        self.addEdge(j + n * 2, y + 1, self.INF)
            else:
                for j in range(max(1, k - n), min(k + 1, n)):
                    if j != a and j != b:
                        self.addEdge(x + 1, j + n, self.INF)
                        self.addEdge(y + 1, j + n, self.INF)

        # 执行拓扑排序求解
        if not self.topoSort(n * 3 + 2):
            return -1

        # 打印答案
        res = []
        for i in range(m):
            a, b, k, flg = edgeList[i]
            x, y = 2 * a - 1, 2 * b - 1
            if self.color[x + 1] < self.color[y + 1]:
                res.append(a)
            else:
                res.append(b)
        return res

时间复杂度:$O(m n)$