捉迷藏
题目描述
核心思路
最小路径点覆盖:选择一些路径,覆盖所有点,并且各个路径的节点之间不能有交集,要求路径数最少
最小路径重复点覆盖:选择一些路径,覆盖所有点,但是各个路径的节点之间允许有交集,要求路径数最少
求解最小路径点覆盖的方法:
结论:DAG的最小路径点覆盖=原图节点数-拆点后形成的二分图的最大匹配数
证明:
这里使用的技巧是拆点。假设原图有 n n n个节点,将每个节点都复制一份放到右边,假设节点 i i i复制后的点为 i ′ i' i′,我们将原图中的边 ( i , j ) (i,j) (i,j)转变为新图中的 ( i , j ′ ) (i,j') (i,j′)。新图必然是一个二分图。
如下图:
考虑原图中的任意一条路径转化到新图中是啥样的呢?
- 每条路径转化到新图中一定对应新图的一个匹配,即每个点只会在一条边中。反之也成立
- 我们可以看到原图中每条路径的终点,对应到新图中的话就是出点没有引出一条出边,即它是新图中左部的非匹配点,例如上图中的节点3。同理左部的每一个非匹配点都对应着原图中的路径。即使是孤立节点也可以看成一个终点,符合要求。我们想要让路径数最少,那么就是让左部的非匹配点数目最少,由于 n = n= n=左部非匹配点数+左部匹配点数,因此必须让左部匹配点数最大,那么也就是找最大匹配,设最大匹配为 m m m,因此左部的非匹配点最少为 n − m n-m n−m,即路径数最少为 n − m n-m n−m。因此最小路径点覆盖=原图节点数-拆点后形成的二分图的最大匹配数
求解最小路径重复点覆盖的方法:
- 对原图 G G G求一次传递闭包,得到一张图 G ′ G' G′
- 求原图的最小路径重复点覆盖 ⟺ \iff ⟺ 求新图的最小路径点覆盖
证明:
- 充分性:依次考虑原图的每一条符合条件的路径,当我们考察第 i i i条路径时,如果路径上的点和前 i − 1 i-1 i−1条边上的点是重复的,那么直接跳过即可,新图中加了很多边,可以跳过。另外第 i i i条路径上的点不可能全部和前 i − 1 i-1 i−1条边上的点重复,否则第 i i i条路径就没有存在的必要了。
- 必要性:将新图中间接转移过去的边展开成原来的边即可得到原图中的路径。
现在我们来理清一下题意:题目中说到 “如果从房子 A A A沿着路走下去能够到达 B B B,那么在 A A A和 B B B 里的人是能够相互望见的。”这说明了一条路径中最多只能选择一个点作为藏身点,因为如果选择两个藏身点的话,那么就可以互相看见了。 “cl2 要求这 K K K个藏身点的任意两个之间都没有路径相连。”其实就是要求:给定一定DAG图,让我们选择尽可能多的点,使得这些点任意两个点之间都不能相互到达。
题目要求尽可能多的藏身点组成集合,使得任意两点之间都没有边。那这与最小重复点路径覆盖有啥关系嘛?这不应该是最大独立集的含义嘛?但是要注意最大独立集只针对无向图,对于有向图是不适用的。但是题目中说到视线是可以沿着路径无限延展的,似乎在引导我们往传递闭包上面想,而说到传递闭包,那么不就是要求最重复路径点覆盖嘛?
或者我们可以根据下面的这个定理来理清题意:
要求尽可能多的藏身点组成集合,使得任意两点之间都没有边这正与最长反链长度相同,而最长反链长度=最小链覆盖。
我们设答案为 K K K,最小重复路径点覆盖为cnt:
- 先证明 K ≤ c n t K\leq cnt K≤cnt。由于这cnt条路径把所有节点都已经覆盖了,所以选择 K K K个点从这cnt条路径上选了,每条路径上最多只能选择一个点,因此必然有 K ≤ c n t K\leq cnt K≤cnt。
- 再证明
K
=
c
n
t
K=cnt
K=cnt。可以采用构造法:我们把这cnt条路径中每条路径的终点都放入这个顶点集合
V
V
V中。设集合
next(V)
表示:从点集合 V V V中的每个点出发可以到达的所有顶点的集合。- 如果 V ∩ n e x t ( V ) = ∅ V\cap next(V)=\empty V∩next(V)=∅,那么意味着从 V V V中所有节点出发,都不能到达 V V V集合内部的顶点。即 V V V内部的任意两个节点之间是不能相互到达的。而这个含义正好与我们题意想要求的是一致的。因此,而 V V V里面就有cnt个点,因此我们就构造出了包含cnt个点的方案。此时 V V V就是合法的答案。
- 如果 V ∩ n e x t ( V ) ∉ ∅ V\cap next(V)\notin \empty V∩next(V)∈/∅,那么我们从 V V V中挑选出任意一个点 v i v_i vi(某条路径的终点),让 v i v_i vi沿着有向边往回走(往起点方向走),走到某个点 v i ′ v_i' vi′不属于 n e x t ( V ) next(V) next(V)为止。对于 V V V集合中的每一个点 v i v_i vi都进行这样的操作,每个 v i v_i vi都走到 v i ′ v_i' vi′不属于 n e x t ( V ) next(V) next(V)为止。那么最终直到 V V V和 n e x t ( V ) next(V) next(V)没有交集为止。那么如何证明最终一定可以做到 V V V和 n e x t ( V ) next(V) next(V)没有交集呢?如何证明最多走到起点时就能保证 V ∩ n e x t ( V ) = ∅ V\cap next(V)=\empty V∩next(V)=∅呢? 这里采用反证法:假设一直往回走,一直回退到起点,都不能保证某个点 v i ′ v_i' vi′不属于 n e x t ( V ) next(V) next(V),也就是说,走到起点了仍然有某个点 v i ′ ∈ n e x t ( V ) v_i'\in next(V) vi′∈next(V),由于 n e x t ( V ) next(V) next(V)表示从多条路径的终点出发所能到达的所有顶点的集合,那么这说明 v i ′ v_i' vi′所在的这条路径 e e e的起点都是可以被这些路径所到达的,那么这条路径 e e e其实就没有存在的价值了。因为我们可以把这条路径 e e e接到 n e x t ( V ) next(V) next(V)中某条路径的后面,那么当前路径 e e e上的每一个点都可以被覆盖,由于把这条路径接到了某一条路径的后面,因此路径数目少1,这与cnt为最小重复路径点覆盖矛盾。因此,我们就证明了 v i v_i vi它走到某个点就一定会停止,也就是说最终仍然会有 V ∩ n e x t ( V ) = ∅ V\cap next(V)=\empty V∩next(V)=∅。因此,我们就证明了 K = c n t K=cnt K=cnt。
由于我们证明了 K ≤ c n t K\leq cnt K≤cnt并且 K = c n t K=cnt K=cnt,那么最终 K = c n t K=cnt K=cnt。
代码
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=210,M=30010;
int n,m;
//邻接矩阵 g[i][j]=1表示从i节点到j节点连一条有向边
//g[i][j]=0表示从i节点到j节点没没有引出有向边
int g[N][N];
int match[N];
bool st[N];
//寻找男生x的配对女生
bool find(int x)
{
//遍历x能够联系的所有这n个女生
for(int i=1;i<=n;i++)
{
//g[x][i]==1表示男生x和女生i之间连了一条有向边 可以联系
//!st[i]表示男生x之前还没有访问过这个女生i
if(g[x][i]&&!st[i])
{
st[i]=true;
if(match[i]==-1||find(match[i]))
{
match[i]=x;
return true;
}
}
}
return false;
}
int main()
{
memset(match,-1,sizeof match);
scanf("%d%d",&n,&m);
while(m--)
{
int a,b;
scanf("%d%d",&a,&b);
g[a][b]=1;
}
//对原图G求一遍传递闭包,得到一个新图G'
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;
//对这个新图G'求二分图的最大匹配
for(int i=1;i<=n;i++)
{
memset(st,0,sizeof st);
if(find(i))
res++;
}
//最小路径重复点覆盖=总点数-新图G'的最大匹配
printf("%d\n",n-res);
return 0;
}