379. 捉迷藏

题目链接

379. 捉迷藏

Vani 和 cl2 在一片树林里捉迷藏。

这片树林里有 \(N\) 座房子,\(M\) 条有向道路,组成了一张有向无环图。

树林里的树非常茂密,足以遮挡视线,但是沿着道路望去,却是视野开阔。

如果从房子 \(A\) 沿着路走下去能够到达 \(B\),那么在 \(A\)\(B\) 里的人是能够相互望见的。

现在 cl2 要在这 \(N\) 座房子里选择 \(K\) 座作为藏身点,同时 Vani 也专挑 cl2 作为藏身点的房子进去寻找,为了避免被 Vani 看见,cl2 要求这 \(K\) 个藏身点的任意两个之间都没有路径相连。

为了让 Vani 更难找到自己,cl2 想知道最多能选出多少个藏身点。

输入格式

输入数据的第一行是两个整数 \(N\)\(M\)

接下来 \(M\) 行,每行两个整数 \(x,y\),表示一条从 \(x\)\(y\) 的有向道路。

输出格式

输出一个整数,表示最多能选取的藏身点个数。

数据范围

\(N \le 200,M \le 30000\)

输入样例:

7 5
1 2
3 2
2 4
4 5
4 6

输出样例:

3

解题思路

有向无环图的最小路径点覆盖

路径点覆盖:选择若干条不相交(点不相交)的路径,这些路径可以将所有的点覆盖
最小路径点覆盖:即路径最少的路径点覆盖

结论:\(有向无环图的最小路径点覆盖=n-二分图的最大匹配\)
证明:首先要将一个有向无环图转化为一个二分图,不妨拆点:即将一个点拆分为入点和出点,将出点看成二分图的左部,入点看成二分图的右部,原图的所有边都可以转化为二分图的边,由于选择的路经是不相交的,即每个点的出度和入度至多为 \(1\),对应在二分图上,即左部的点向右部连的边是唯一的,即是匹配边,对于一条路径的终点,其出度为 \(0\),对应在二分图即左部的非匹配点,故 \(n-二分图最大匹配\) 即为有向无环图的最小路径点覆盖

扩展:\(\color{red}{有向无环图的最小路径重复点覆盖?}\)
结论:记原图 \(G\),求传递闭包后的图记为 \(G'\),求 \(G\) 的最小路径重复点覆盖等价于求 \(G'\) 的最小路径点覆盖
证明:
\(G'\) 的路径点覆盖 $\rightarrow $ \(G\) 的路径重复点覆盖
传递闭包即对于 \(x->y->z\),这时 \(x->z\),求出 \(G'\) 的路径点覆盖后,凡是涉及 \(x->z\) 这样的边,都扩展开来,即对应 \(G\) 的路径重复点覆盖

\(G\) 的最小路径重复点覆盖 $\rightarrow $ \(G'\) 的最小路径点覆盖
对于当前路径 \(\dots->x->p->y->\dots\),如果存在重复点 \(p\),即前面有这样的路径 \(\dots->a->p->b->\dots\),则当前路径可以跳过 \(p\),因为 \(p\) 已经被选了,即对应 \(G'\) 中含传递闭包的路径
故两者可以互相转换,求 \(G\) 的最小路径重复点覆盖即可转化为求 \(G'\) 的最小路径点覆盖

本题要找最多的点,使得两两之间不存在可达的边,设 \(G\) 的最小路径重复点覆盖为 \(x\),则要找的点不可能比 \(x\) 大,因为 \(G\) 的最小路径重复点覆盖包含了所有的点,且每一条路径上最多只能选择一个点,否则由鸽笼原理,一定会存在一条可达的路径,后面构造使得:\(这样的点=x\),将最小重复点覆盖路径的终点的集合即为 \(E\),设 \(E\) 中的所有点可到的点的集合为 \(next(E)\),如果 $E\bigcap next(E)=\varnothing $,即 \(E\) 中所有的点都到不了 \(E\) 本身的某一个点,即 \(E\) 中所有点互不可达;如果 $E\bigcap next(E)\neq\varnothing $,每次选择一条终点可到 \(E\) 的一条路径,不选这个点,往回退选点,所有这样的终点往回退选点,直到 $E\bigcap next(E)=\varnothing $ 为止,\(\color{red}{这样是否可行?}\)假设存在这样一条路径,其往回退退到起点依然所有的点仍在 \(next(E)\) 中,说明这条路径上的所有的点都可以被覆盖掉,这与最小路径重复点覆盖矛盾,所以这样的方法可行,即一定会有 \(E\bigcap next(E)=\varnothing\),故答案即为最小路径重复点覆盖

  • 时间复杂度:\(O(nm)\)

代码

// Problem: 捉迷藏
// Contest: AcWing
// URL: https://www.acwing.com/problem/content/381/
// Memory Limit: 64 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

// %%%Skyqwq
#include <bits/stdc++.h>
 
//#define int long long
#define help {cin.tie(NULL); cout.tie(NULL);}
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
 
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<LL, LL> PLL;
 
template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
 
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

const int N=205;
int n,m;
bool g[N][N],st[N];
int match[N];
bool find(int x)
{
	for(int i=1;i<=n;i++)
		if(g[x][i])
		{
			if(!st[i])
			{
				st[i]=true;
				if(match[i]==0||find(match[i]))
				{
					match[i]=x;
					return true;
				}
			}
		}
	return false;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
    	int x,y;
    	scanf("%d%d",&x,&y);
    	g[x][y]=true;
    }
    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=n;
    for(int i=1;i<=n;i++)
    {
    	memset(st,0,sizeof st);
    	if(find(i))res--;
    }
    printf("%d",res);
    return 0;
}
posted @ 2022-11-29 23:03  zyy2001  阅读(42)  评论(0编辑  收藏  举报