\(AcWing\) \(379\) 捉迷藏
ACwing 379. 捉迷藏 有向无环图的最小路径点(可重复)覆盖+传递闭包
一、题目描述
\(Vani\) 和 \(cl2\) 在一片树林里捉迷藏。
这片树林里有 \(N\) 座房子,\(M\) 条有向道路,组成了一张 有向无环图(\(DAG\))。
树林里的树非常茂密,足以遮挡视线,但是沿着道路望去,却是视野开阔。
如果从房子 \(A\) 沿着路走下去能够到达 \(B\),那么在 \(A\) 和 \(B\) 里的人是能够相互望见的。
现在 \(cl2\) 要在这 \(N\) 座房子里选择 \(K\) 座作为藏身点,同时 \(Vani\) 也专挑 \(cl2\) 作为藏身点的房子进去寻找,为了避免被 \(Vani\) 看见,\(cl2\) 要求这 \(K\) 个藏身点的任意两个之间都没有路径相连。
为了让 \(Vani\) 更难找到自己,\(cl2\) 想知道最多能选出多少个藏身点。
输入格式
输入数据的第一行是两个整数 \(N\) 和 \(M\)。
接下来 \(M\) 行,每行两个整数 \(x,y\),表示一条从 \(x\) 到 \(y\) 的有向道路。
输出格式
输出一个整数,表示最多能选取的藏身点个数。
数据范围
\(N≤200,M≤30000\)
输入样例:
7 5
1 2
3 2
2 4
4 5
4 6
输出样例:
3
二、最小路径覆盖
最小路径点覆盖
概念:
对于一个有向无环图,用最少的互不相交的路径将所有的点覆盖。(这里的最少的互不相交的路径是指:边不重复,点也不重复)。
拆点:对于原图中\((1, n)\)拆为新图中的\((1', n')\);
转化:将原图中的\(i -> j\) 转化为新图中的\(i -> j'\)。
如图将原图中的\(1 -> 2 -> 3\)转化为新图的路径即为:
\(1 -> 2', 2 -> 3'\)。
但是不能出现\(1 -> 2, 3 -> 2\)这样点就重复了
因此得出两个结论:
-
1:路径 $\Leftrightarrow $ 匹配
-
2:左部非匹配点 $\Leftrightarrow $ 路径终点,孤立的点也算一种特别的终点,比如\(5\)
所以要求原图中最少的互不相交的路径,即原图中终点最少的路径,即求新图中左侧最少的非匹配点的数量\((n - m)\),即求新图中左侧最大的匹配数量(\(m\)),即求最大匹配数(\(m\)), 最后用左侧所有点数(\(n\)) - 最大匹配数(\(m\))即为最少的互不相交的路径
在二分图中,最小路径点覆盖=总点数-最大匹配数
最小路径点 重复 覆盖
概念
给定一张有向无环图,要求使用尽量少的可相交的简单路径,覆盖有向无环图的所有顶点(也就是每个顶点可以覆盖多次)。这个问题被称为有向无环图的 最小路径可重复点覆盖
定理一
有向无环图\(G\)的最小路径点覆盖包含的路径条数
等于 \(n\) 减去 拆点二分图\(G2\) 的最大匹配数
\(G2\)左部点代表着每条有向边起点,右部点代表每条有向边终点。
定理二
有向无环图\(G\)的最小路径可 重复点覆盖 包含的路径条数
求解步骤:
① 先对有向图传递闭包得到\(G'\)
② 再进行最小路径点覆盖的操作
四、本题思路
这是一个 最小路径可重复问题, 首先要进行 传递闭包 将其 转换为不可重复问题
传递闭包
通俗的讲就是如果\(a->b, b->c\),那么我们就建立一条\(a->c\)的边。将所有能间接相连的点直接相连。 \(Floyd\)能在\(O(n^3)\) 求出一个图的传递闭包。
将原图进行传递闭包之后图会额外增加右图的边
那么新图(左\(+\)右\(=\)新图)中的 最小路径点覆盖问题 其实就是 左图中 重复覆盖所有点最少的路径数量
因为每个路径终点不会 相互到达,所以 路径数就等于终点数, 现在想求 最少的终点数,也就是求 最少的路径数
因此,这些终点就是保证互相到达不了的能够选取的 藏身点个数
为什么这个问题是一个最小路径可重复覆盖? 因为题目中有说,如果一个点沿着某一路径走下去可以到达另一个点,则这两个点也是互相可以望见的,也就是说对于边\(a->b,b->c\),间接的\(a->c\)也算。所以就先\(floyd\)求一下传递闭包,建立拆点二分图,然后跑一遍 最小路径点覆盖。
五、实现代码
#include <bits/stdc++.h>
using namespace std;
const int N = 210, M = 30010;
int n, m;
int g[N][N], st[N];
int match[N];
bool find(int x) {
for (int i = 1; i <= n; i++) {
if (g[x][i] && !st[i]) {
st[i] = true;
int t = match[i];
if (t == -1 || find(t)) {
match[i] = x;
return true;
}
}
}
return false;
}
int main() {
scanf("%d %d", &n, &m);
memset(match, -1, sizeof match);
while (m--) {
int a, b;
scanf("%d %d", &a, &b);
g[a][b] = 1;
}
// floyd求传递闭包
for (int k = 1; k <= n; k++)
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
g[i][j] |= g[i][k] & g[k][j];
int res = 0;
for (int i = 1; i <= n; i++) {
memset(st, 0, sizeof st);
if (find(i)) res++;
}
printf("%d\n", n - res);
return 0;
}