题目链接
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;
}
标签:二分,覆盖,捉迷藏,路径,最小,379,int,重复
From: https://www.cnblogs.com/zyyun/p/16937058.html