AcWing 379 捉迷藏


题目传送门

本题的数学证明
个人理解,目前\(NOIP\)级别的选手,不用玩数字证明吧,能记下结论就很牛\(X\)了~

一、概念与定理

最小路径覆盖:用最少互不相交的路径,将点全部覆盖。

最小路径重复覆盖:用最少多少条路径覆盖所有点,路径可以有公共点和公共边

在二分图中:最小路径覆盖=总点数-最大匹配数

\(DAG\)\(G\)中的最小路径重复覆盖==在他的传递闭包图\(G'\)中的最小路径覆盖

二、套路与方法

最小路径重复覆盖 = 点数 - 最大匹配数,要用\(floyd\)求闭包,再跑匈牙利求最大匹配。

二、本题思路

每条路径上的任意点都可以相互看到。那么我们可以求出这个图的最小路径重复点覆盖的条数\(cnt\)。 每条路径上我们至少可以选出一个点。 故藏身点的数量就等于 最小路径重复点覆盖的条数。

三、实现代码

#include 
using namespace std;
const int N = 207, M = 30007;
int n, m;
bool d[N][N], st[N];
int match[N];

bool find(int x) {
    for (int i = 1; i <= n; i++) {
        if (d[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() {
    cin >> n >> m;
    memset(match, -1, sizeof match);
    // memset(d,false,sizeof d);

    for (int i = 0; i < m; i++) {
        int a, b;
        cin >> a >> b;
        d[a][b] = true;
    }
    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                d[i][j] |= d[i][k] & d[k][j];

    int res = 0;
    for (int i = 1; i <= n; i++) {
        memset(st, 0, sizeof st);
        if (find(i)) res++;
    }
    cout << n - res << endl;
    return 0;
}