📜  门| GATE CS 2021 |设置 2 |第 35 题(1)

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

门 | GATE CS 2021 |设置 2 |第 35 题

本题是2021年Gate计算机科学考试第35题,考察了求解最小路径覆盖(Minimum Path Cover)的问题。

题目描述

给定一个有向无环图(DAG),我们希望通过尽可能少的顶点来覆盖所有的边。找到最小路径覆盖。

输入

输入的第一行是T,表示测试用例的数量。每个测试用例的第一行由两个整数N和M组成,分别表示DAG的节点数和边数。接下来M行各包含两个整数u和v,表示DAG中从u到v有一条有向边。

输出

对于每个测试用例,输出一行一个整数,表示最小路径覆盖需要的路径数。

示例

输入示例:

2
3 3
1 2
2 3
1 3
4 3
1 2
2 3
3 4

输出示例:

2
2
解题思路

最小路径覆盖问题可以被转化为二分图最大匹配问题。具体来说,首先将DAG的每个节点u拆成入点u.in和出点u.out。对于每条有向边(u,v),我们向左边的出点u.out连一条边,向右边的入点v.in连一条边。由此,我们得到了一个二分图。最小路径覆盖问题等价于将这个二分图的节点分成若干个不相交的组,使得每个组都是匹配的。José R. Correa将这个等价性证明如下:

对于路径覆盖问题中的一条路径,我们对应着二分图中的一条交替路径。反过来,对于一个匹配,我们可以将其分成若干个不相交交替路径,这些交替路径对应着DAG中的路径。这里的交替路径指的是对于该路径上的任意两个相邻节点,一个是入点一个是出点。

因此,我们可以先将二分图中任意一个匹配找出来,然后不断从中找出任意一条包含最小数量入点或出点的交替路径去增广,直到所有节点都被匹配。这个过程类似于匈牙利算法。

代码实现

以下是JAVA代码实现,用到了深度优先搜索和匈牙利算法:

import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Vector;

public class Main {
    static int t, n, m, cnt;
    static Vector<Integer> g[];
    static boolean vis[];
    static int match[];
    static enum Node {in, out} // 枚举节点类型

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        t = Integer.parseInt(br.readLine());
        while (t-- > 0) {
            String s[] = br.readLine().split(" ");
            n = Integer.parseInt(s[0]);
            m = Integer.parseInt(s[1]);
            g = new Vector[n*2];
            vis = new boolean[n*2];
            match = new int[n*2];
            cnt = 0;
            for (int i = 0; i < n*2; i++) {
                g[i] = new Vector<Integer>();
            }
            while (m-- > 0) {
                s = br.readLine().split(" ");
                int u = Integer.parseInt(s[0]) - 1, v = Integer.parseInt(s[1]) - 1;
                g[u*2+Node.out.ordinal()].add(v*2+Node.in.ordinal()); // 左边的出点向右边的入点连边
            }
            for (int i = 0; i < n*2; i++) {
                if (!vis[i]) {
                    dfs(i);
                }
            }
            System.out.println(n-cnt/2); // cnt算的是最大匹配的边数,除以2得到点数
        }
    }

    static boolean dfs(int u) {
        vis[u] = true;
        for (int v : g[u]) {
            if (match[v] == 0 || !vis[match[v]] && dfs(match[v])) {
                match[u] = v;
                match[v] = u;
                return true;
            }
        }
        return false;
    }
}