📜  门|门CS 2011 |第 62 题(1)

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

题目介绍

题目名称:门|门CS 2011 |第62题

题目类型:算法题

难度评级:中等

题目描述

在一个由0和1组成的矩阵中,0表示门,1表示路。现在有一批牛要走过这个矩阵,每个牛只能进出一扇门,同时,每个门只能被一头牛进出。你的任务是安排每头牛进出门的方案,使得走出这个矩阵的最大缩短距离最小。

输入格式

第一行包含三个整数n,m,k,表示矩阵的大小为n×m,有k扇门。

接下来k行,每行两个整数$x_i,y_i$,表示第$i$扇门的位置坐标为$(x_i,y_i)$,保证这些坐标互不相同。

接下来n行,每行m个字符0或1,表示矩阵,保证每个空间都可以到达一扇门。

输出格式

一行一个整数,表示最大缩短距离最小的值(结果保留一位小数)。

样例

输入样例:

4 4 2
4 4
2 4
1111
1001
0111
1110

输出样例:

4.0
题目解析

这道题可以暴力枚举每头牛选择的门的方式,再依次计算牛走过矩阵的最短路径。但是,这样的时间复杂度是指数级别的,效率不高。

更好的方法是使用二分答案。首先我们假设最小的缩短距离是mid,然后对所有小于等于mid的门与连通块(连通块指没有门的部分)连边,建立一个新的网络。

使用最小割算法,求出网络中的最小割,如果所有连通块都和某个门连通,说明mid可以满足要求。否则,说明mid太小了,割掉的边数不够,需要缩小mid的值。

重复这个过程,使用二分答案可以使时间复杂度降为$\mathcal{O}(k \cdot n^3 \cdot \log_2 w)$,其中,$w$是最大的距离。

参考代码
from queue import Queue


class Dinic:
    def __init__(self, n):
        self.n = n
        self.edges = [[] for _ in range(n)]
        self.visited = [False] * n
        self.depth = [0] * n
        self.ptr = [0] * n

    def add_edge(self, u, v, cap):
        self.edges[u].append({'to': v, 'cap': cap, 'rev': len(self.edges[v])})
        self.edges[v].append({'to': u, 'cap': 0, 'rev': len(self.edges[u]) - 1})

    def bfs(self, s):
        self.visited = [False] * self.n
        q = Queue()
        q.put(s)
        self.visited[s] = True
        self.depth[s] = 0
        while not q.empty():
            v = q.get()
            for e in self.edges[v]:
                if e['cap'] > 0 and not self.visited[e['to']]:
                    self.visited[e['to']] = True
                    self.depth[e['to']] = self.depth[v] + 1
                    q.put(e['to'])

    def dfs(self, v, t, f):
        if v == t:
            return f
        for i in range(self.ptr[v], len(self.edges[v])):
            e = self.edges[v][i]
            if e['cap'] > 0 and self.depth[v] < self.depth[e['to']]:
                d = self.dfs(e['to'], t, min(f, e['cap']))
                if d > 0:
                    e['cap'] -= d
                    self.edges[e['to']][e['rev']]['cap'] += d
                    return d
            self.ptr[v] += 1
        return 0

    def max_flow(self, s, t):
        flow = 0
        while True:
            self.bfs(s)
            if not self.visited[t]:
                break
            self.ptr = [0] * self.n
            while True:
                f = self.dfs(s, t, float('inf'))
                if f == 0:
                    break
                flow += f
        return flow


def can_pass(mid, gates, graph):
    n = len(graph)
    s, t = n, n + 1
    dinic = Dinic(n + 2)
    for i, gi in enumerate(gates):
        dinic.add_edge(s, gi, float('inf'))
        for j in range(n):
            if graph[gi][j] <= mid and (j not in gates):
                dinic.add_edge(gi, j, float('inf'))
    for i in range(n):
        if i not in gates:
            dinic.add_edge(i, t, float('inf'))
    return dinic.max_flow(s, t) == sum([1 for gi in gates if dinic.visited[gi]])


def main():
    n, m, k = map(int, input().split())
    gates = []
    graph = []
    regions = []
    for i in range(k):
        x, y = map(int, input().split())
        x -= 1
        y -= 1
        gates.append(x * m + y)
    for i in range(n):
        row = input().strip()
        region = []
        for j in range(m):
            if row[j] == '0':
                region.append(float('inf'))
            else:
                region.append(0)
        graph.append(region)
    for i in range(n):
        for j in range(m):
            if graph[i][j] == 0:
                if i > 0:
                    graph[i][j] = min(graph[i][j], graph[i - 1][j] + 1)
                if j > 0:
                    graph[i][j] = min(graph[i][j], graph[i][j - 1] + 1)
            regions.append(graph[i][j])
    regions = sorted(set(regions))
    l = 0
    r = len(regions) - 1
    while l < r:
        mid = (l + r) // 2
        if can_pass(regions[mid], gates, graph):
            r = mid
        else:
            l = mid + 1
    print('%.1f' % regions[l])


if __name__ == '__main__':
    main()

代码片段注释:

  1. Dinic 最大流算法的实现
from queue import Queue


class Dinic:
    def __init__(self, n):
        self.n = n
        self.edges = [[] for _ in range(n)]
        self.visited = [False] * n
        self.depth = [0] * n
        self.ptr = [0] * n

    def add_edge(self, u, v, cap):
        self.edges[u].append({'to': v, 'cap': cap, 'rev': len(self.edges[v])})
        self.edges[v].append({'to': u, 'cap': 0, 'rev': len(self.edges[u]) - 1})

    def bfs(self, s):
        self.visited = [False] * self.n
        q = Queue()
        q.put(s)
        self.visited[s] = True
        self.depth[s] = 0
        while not q.empty():
            v = q.get()
            for e in self.edges[v]:
                if e['cap'] > 0 and not self.visited[e['to']]:
                    self.visited[e['to']] = True
                    self.depth[e['to']] = self.depth[v] + 1
                    q.put(e['to'])

    def dfs(self, v, t, f):
        if v == t:
            return f
        for i in range(self.ptr[v], len(self.edges[v])):
            e = self.edges[v][i]
            if e['cap'] > 0 and self.depth[v] < self.depth[e['to']]:
                d = self.dfs(e['to'], t, min(f, e['cap']))
                if d > 0:
                    e['cap'] -= d
                    self.edges[e['to']][e['rev']]['cap'] += d
                    return d
            self.ptr[v] += 1
        return 0

    def max_flow(self, s, t):
        flow = 0
        while True:
            self.bfs(s)
            if not self.visited[t]:
                break
            self.ptr = [0] * self.n
            while True:
                f = self.dfs(s, t, float('inf'))
                if f == 0:
                    break
                flow += f
        return flow
  1. 二分答案部分的实现
def can_pass(mid, gates, graph):
    n = len(graph)
    s, t = n, n + 1
    dinic = Dinic(n + 2)
    for i, gi in enumerate(gates):
        dinic.add_edge(s, gi, float('inf'))
        for j in range(n):
            if graph[gi][j] <= mid and (j not in gates):
                dinic.add_edge(gi, j, float('inf'))
    for i in range(n):
        if i not in gates:
            dinic.add_edge(i, t, float('inf'))
    return dinic.max_flow(s, t) == sum([1 for gi in gates if dinic.visited[gi]])


def main():
    n, m, k = map(int, input().split())
    gates = []
    graph = []
    regions = []
    for i in range(k):
        x, y = map(int, input().split())
        x -= 1
        y -= 1
        gates.append(x * m + y)
    for i in range(n):
        row = input().strip()
        region = []
        for j in range(m):
            if row[j] == '0':
                region.append(float('inf'))
            else:
                region.append(0)
        graph.append(region)
    for i in range(n):
        for j in range(m):
            if graph[i][j] == 0:
                if i > 0:
                    graph[i][j] = min(graph[i][j], graph[i - 1][j] + 1)
                if j > 0:
                    graph[i][j] = min(graph[i][j], graph[i][j - 1] + 1)
            regions.append(graph[i][j])
    regions = sorted(set(regions))
    l = 0
    r = len(regions) - 1
    while l < r:
        mid = (l + r) // 2
        if can_pass(regions[mid], gates, graph):
            r = mid
        else:
            l = mid + 1
    print('%.1f' % regions[l])


if __name__ == '__main__':
    main()

代码中的 can_pass() 函数用于判断是否可以把某个门与连通块之间的路径缩短到小于等于midmain()函数中,regions用于保存矩阵中每个格子到最近的门的距离,然后对regions进行排序,然后使用二分答案,对中间的值调用can_pass()函数来判断是否存在方案,从而确定最大缩短距离的最小值。