📜  门|门CS 2012 |第 63 题(1)

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

门|门CS 2012 |第 63 题

题目描述

给定一个 $n$ 个点 $m$ 条边的有向图,边权都是 $1$。再给定 $m$ 个询问,每次询问给出两个点 $u,v$,要求你求出从 $u$ 出发到 $v$ 的路径中,经过的边至少在 $k$ 个询问中被询问过。

输入格式

第一行两个整数 $n,m$,表示点数和边数。 接下来 $m$ 行,每行两个整数 $u,v$,表示一条从 $u$ 到 $v$ 的有向边。

接下来一行一个整数 $q$,表示询问个数。

接下来 $q$ 行,每行三个整数 $u,v,k$,表示一组询问。

输出格式

输出一行,包含 $q$ 个整数,其中第 $i$ 个数表示第 $i$ 个询问的答案。

数据范围

$1\leq n,m\leq 10^5$,

$1\leq q\leq 10^4$

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

本题要求路径经过的边至少在 $k$ 个询问中被询问过,在无权图中,最短路可以使用 BFS 算法求解,但是本题是带权图,因此不能使用 BFS。

我们可以使用二分图匹配求解,将每个询问所对应的路径中的所有边都添加到图中,如果存在一个二分图,它能够满足所匹配的每条边都至少被 $k$ 个询问经过,那么这个二分图所对应的匹配方案即为满足条件的。

而对于每个询问,则可以将其看作寻找 $u$ 到 $v$ 的一条链,链上要求经过的边数量至少为 $k$,这种问题可以使用二分答案求解。

因此,在本题中,我们可以使用二分答案 + 二分图匹配的方法来解决问题。

详细实现可以参考以下代码:

from collections import defaultdict
import bisect

# 统计每条边被包含在了哪些询问中
def get_edges_questions(edges, questions):
    edges_questions = [set() for _ in range(len(edges))]
    for i, (a, b) in enumerate(edges):
        for j, (u, v, k) in enumerate(questions):
            if a == u and b == v:
                edges_questions[i].add(j)
    return edges_questions

# 寻找是否有一个二分图,满足匹配的每条边都至少被 k 个询问所包含
def can_match_k(edges_questions, mid):
    # s -> u, t -> v
    for u, qs in enumerate(edges_questions):
        for v in range(len(edges_questions)):
            if u != v:
                matches = [1 if q in qs or q in edges_questions[v] else 0 for q in range(len(questions))]
                if sum(matches) >= mid and len(questions) - sum(matches) >= k:
                    bipartite.append((u, v))
    return max_bipartite_match()

# 二分答案 + 二分图匹配
def binary_search_match(edges_questions, questions):
    L = 0
    R = len(questions)
    result = []
    while L < R:
        mid = (L + R + 1) // 2
        bipartite.clear()
        if can_match_k(edges_questions, mid):
            L = mid
        else:
            R = mid - 1
    return result

# 构建二分图
def max_bipartite_match():
    # 从左半部分的一个点出发,尽量将右半部分的点都匹配出去
    # 这里可以选择使用广度优先搜索,也可以使用深度优先搜索
    def dfs(u, matched):
        for i in range(len(bipartite)):
            v = bipartite[i][1]
            if i not in matched and u == bipartite[i][0]:
                matched.add(i)
                if not (v in occupied and dfs(occupied[v], matched)):
                    occupied[v] = i
                    return True
        return False

    max_match = 0
    occupied = {}
    for i in range(len(edges_questions)):
        if dfs(i, set()):
            max_match += 1
    return max_match
    
# 解析输入
n, m = map(int, input().split())
edges = [tuple(map(int, input().split())) for _ in range(m)]
q = int(input())
questions = [list(map(int, input().split())) for _ in range(q)]

# 获取每个边被包含在了哪些询问中
edges_questions = get_edges_questions(edges, questions)

# 二分答案 + 二分图匹配
binary_search_match(edges_questions, questions)

# 输出答案
for m in result:
    print(m, end=' ')

代码中用到了一个 max_bipartite_match 函数,它是使用 DFS 实现的二分图匹配,但这个函数并没有在代码中实际使用到,二分图匹配的实现和应用都在 can_match_k 函数中进行了。

这段代码另外一个需要注意的地方是循环的条件,对于二分答案,要使用 while L < R 循环,而不是 while L <= R,这是因为使用前者可以使答案向上取整,得到一个更加合理的答案。

最后,输出答案时,需要使用 print(m, end=' '),这是因为需要将每个答案之间加上一个空格,而 print 的默认行为是输出换行符,因此需要通过 end 参数来修改默认行为。