📜  门|门CS 2012 |问题 25(1)

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

门|门CS 2012 |问题 25

该问题属于Codeforces平台的比赛题目,旨在考察参赛者的编程能力和算法能力。该问题的难度为1600,属于中等难度。

问题描述

有 $n$ 扇门,编号从 $1$ 到 $n$。每扇门有若干把锁,只有所有锁都被打开,门才能被打开。共有 $m$ 把钥匙,编号从 $1$ 到 $m$。每把钥匙只能打开其中的一扇门上的一个锁。

现在给出 $k$ 个人的想要开哪些门,第 $i$ 个人的想法为:打开编号为 $a_{i,1},a_{i,2},...,a_{i,x_i}$ 的门。每个人都有无限个钥匙,但是每个人只能打开他自己想要打开的门。

试问最少需要多少钥匙才能使得所有人都能打开他们想要打开的门。

输入格式

输入共有 $k+1$ 行,第一行包含三个整数 $n,m,k$,表示门的数量、钥匙的数量和人的数量。

接下来 $k$ 行,每行包含若干个整数,用空格隔开。第 $i$ 行的 $x_i$ 表示第 $i$ 个人有 $x_i$ 扇门想要打开,接下来 $x_i$ 个数字表示第 $i$ 个人所有想要打开的门的编号。

输出格式

输出一个整数,表示最少需要多少钥匙才能使得所有人都能打开他们想要打开的门。

样例输入1
3 6 2
2 2 3
2 1 2
样例输出1
2
样例输入2
6 9 3
3 1 4 6
3 2 4 6
3 3 4 6
样例输出2
3
解题思路

本题需要使用最小割算法来解决。我们将每把钥匙抽象为图中的一条边,每扇门看作是一个节点,每个人需要打开的门看作是一条源点到门节点的有向边。同时,我们也将源点和汇点加入到图中,源点到每个人对应的节点有一条有向边,汇点从每扇门的节点出发有一条有向边。

接下来,我们对这个图求最小割即可。最小割的值即为最少需要的钥匙数。

代码实现
from collections import defaultdict

class Dinic:
    INF = 1 << 30

    def __init__(self, n):
        self.n = n
        self.edges = defaultdict(list)
        self.level = [-1] * (n + 1)
        self.ptr = [0] * (n + 1)

    def add_edge(self, u, v, c):
        self.edges[u].append({"dest": v, "capacity": c, "next": len(self.edges[v])})
        self.edges[v].append({"dest": u, "capacity": 0, "next": len(self.edges[u]) - 1})

    def bfs(self, s, t):
        self.level = [-1] * (self.n + 1)
        self.level[s] = 0
        q = [s]
        while q:
            u = q.pop(0)
            for e in self.edges[u]:
                v = e["dest"]
                if self.level[v] == -1 and e["capacity"] > 0:
                    self.level[v] = self.level[u] + 1
                    q.append(v)
        return self.level[t] != -1

    def dfs(self, u, t, f):
        if u == t:
            return f
        for i in range(self.ptr[u], len(self.edges[u])):
            e = self.edges[u][i]
            v = e["dest"]
            if e["capacity"] > 0 and self.level[v] == self.level[u] + 1:
                incf = self.dfs(v, t, min(f, e["capacity"]))
                if incf:
                    self.edges[u][i]["capacity"] -= incf
                    self.edges[v][e["next"]]["capacity"] += incf
                    return incf
        return 0

    def max_flow(self, s, t):
        max_flow = 0
        while self.bfs(s, t):
            self.ptr = [0] * (self.n + 1)
            while True:
                incf = self.dfs(s, t, self.INF)
                if not incf:
                    break
                max_flow += incf
        return max_flow

n, m, k = map(int, input().split())

S = n + m + k + 1
T = n + m + k + 2

network_flow = Dinic(n + m + k + 2)

for i in range(1, k + 1):
    ableDoors = set(map(int, input().split()[1:]))
    network_flow.add_edge(S, i, 1)
    for j in ableDoors:
        network_flow.add_edge(i, k + j, 1)

for i in range(1, n + 1):
    network_flow.add_edge(k + i, T, 1)

for i in range(1, m + 1):
    network_flow.add_edge(S, k + i, 1)

print(network_flow.max_flow(S, T))

解释:

  • 核心算法:最小割算法
  • 核心数据结构:Dinic网络流

完整解法代码可以从我的GitHub获取。