捉迷藏--

捉迷藏


题目描述

image-20210720123116058


核心思路

最小路径点覆盖:选择一些路径,覆盖所有点,并且各个路径的节点之间不能有交集,要求路径数最少

最小路径重复点覆盖:选择一些路径,覆盖所有点,但是各个路径的节点之间允许有交集,要求路径数最少

求解最小路径点覆盖的方法:

结论:DAG的最小路径点覆盖=原图节点数-拆点后形成的二分图的最大匹配数

证明:

这里使用的技巧是拆点。假设原图有 n n n个节点,将每个节点都复制一份放到右边,假设节点 i i i复制后的点为 i ′ i' i,我们将原图中的边 ( i , j ) (i,j) (i,j)转变为新图中的 ( i , j ′ ) (i,j') (i,j)。新图必然是一个二分图。

如下图:

image-20210720123914525

考虑原图中的任意一条路径转化到新图中是啥样的呢?

  • 每条路径转化到新图中一定对应新图的一个匹配,即每个点只会在一条边中。反之也成立
  • 我们可以看到原图中每条路径的终点,对应到新图中的话就是出点没有引出一条出边,即它是新图中左部的非匹配点,例如上图中的节点3。同理左部的每一个非匹配点都对应着原图中的路径。即使是孤立节点也可以看成一个终点,符合要求。我们想要让路径数最少,那么就是让左部的非匹配点数目最少,由于 n = n= n=左部非匹配点数+左部匹配点数,因此必须让左部匹配点数最大,那么也就是找最大匹配,设最大匹配为 m m m,因此左部的非匹配点最少为 n − m n-m nm,即路径数最少为 n − m n-m nm。因此最小路径点覆盖=原图节点数-拆点后形成的二分图的最大匹配数

求解最小路径重复点覆盖的方法:

  • 对原图 G G G求一次传递闭包,得到一张图 G ′ G' G
  • 求原图的最小路径重复点覆盖    ⟺    \iff 求新图的最小路径点覆盖

证明:

  1. 充分性:依次考虑原图的每一条符合条件的路径,当我们考察第 i i i条路径时,如果路径上的点和前 i − 1 i-1 i1条边上的点是重复的,那么直接跳过即可,新图中加了很多边,可以跳过。另外第 i i i条路径上的点不可能全部和前 i − 1 i-1 i1条边上的点重复,否则第 i i i条路径就没有存在的必要了。
  2. 必要性:将新图中间接转移过去的边展开成原来的边即可得到原图中的路径。

image-20210720145120840

现在我们来理清一下题意:题目中说到 “如果从房子 A A A沿着路走下去能够到达 B B B,那么在 A A A B B B 里的人是能够相互望见的。”这说明了一条路径中最多只能选择一个点作为藏身点,因为如果选择两个藏身点的话,那么就可以互相看见了。 “cl2 要求这 K K K个藏身点的任意两个之间都没有路径相连。”其实就是要求:给定一定DAG图,让我们选择尽可能多的点,使得这些点任意两个点之间都不能相互到达。

题目要求尽可能多的藏身点组成集合,使得任意两点之间都没有边。那这与最小重复点路径覆盖有啥关系嘛?这不应该是最大独立集的含义嘛?但是要注意最大独立集只针对无向图,对于有向图是不适用的。但是题目中说到视线是可以沿着路径无限延展的,似乎在引导我们往传递闭包上面想,而说到传递闭包,那么不就是要求最重复路径点覆盖嘛?

或者我们可以根据下面的这个定理来理清题意:

image-20210720130243580

要求尽可能多的藏身点组成集合,使得任意两点之间都没有边这正与最长反链长度相同,而最长反链长度=最小链覆盖。

我们设答案为 K K K,最小重复路径点覆盖为cnt:

  • 先证明 K ≤ c n t K\leq cnt Kcnt。由于这cnt条路径把所有节点都已经覆盖了,所以选择 K K K个点从这cnt条路径上选了,每条路径上最多只能选择一个点,因此必然有 K ≤ c n t K\leq cnt Kcnt
  • 再证明 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 Vnext(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 Vnext(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 Vnext(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) vinext(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 Vnext(V)=。因此,我们就证明了 K = c n t K=cnt K=cnt

由于我们证明了 K ≤ c n t K\leq cnt Kcnt并且 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;   
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卷心菜不卷Iris

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值