UVA - 11419 SAM I AM (最小点覆盖集)

9 篇文章 0 订阅

题目网址点击打开链接

关于这个题,首先思路是将每个行和列分开来看,即将列和行分为两个部分,两个部分之间的边就是题目中的炸弹,连接其所在的行和列。寻找最少的点可以覆盖所有的边,即是最小点覆盖集,

在做这个题以前,首先要知道一下几点:


1增广路:

(引用)

二分图:简单来说,如果图中点可以被分为两组,并且使得所有边都跨越组的边界,则这就是一个二分图。准确地说:把一个图的顶点划分为两个不相交集 U V ,使得每一条边都分别连接 U V

中的顶点。如果存在这样的划分,则此图为一个二分图。二分图的一个等价定义是:不含有「含奇数条边的环」的图。图 1 是一个二分图。为了清晰,我们以后都把它画成图 2 的形式。

匹配:在图论中,一个「匹配」(matching)是一个边的集合,其中任意两条边都没有公共顶点。例如,图 3、图 4 中红色的边就是图 2 的匹配。

Bipartite Graph(1)  Bipartite Graph(2)  Matching  Maximum Matching

我们定义匹配点、匹配边、未匹配点、非匹配边,它们的含义非常显然。例如图 3 中 1、4、5、7 为匹配点,其他顶点为未匹配点;1-5、4-7为匹配边,其他边为非匹配边。

最大匹配:一个图所有匹配中,所含匹配边数最多的匹配,称为这个图的最大匹配。图 4 是一个最大匹配,它包含 4 条匹配边。

5

增广路:从一个未匹配点出发,走交替路,如果途径另一个未匹配点(出发的点不算),则这条交替路称为增广路(agumenting path)。例如,图 5 中的一条增广路如图 6 所示(图中的匹配点均用红色标出):

6

增广路有一个重要特点:非匹配边比匹配边多一条。因此,研究增广路的意义是改进匹配。只要把增广路中的匹配边和非匹配边的身份交换即可。由于中间的匹配节点不存在其他相连的匹配边,所以这样做不会破坏匹配的性质。交换后,图中的匹配边数目比原来多了 1 条。

我们可以通过不停地找增广路来增加匹配中的匹配边和匹配点。找不到增广路时,达到最大匹配(这是增广路定理)。匈牙利算法正是这么做的。


 2  König定理:最小点覆盖数 = 最大匹配数。

关于定理的证明,借用一个博客1  点击打开链接   点击打开链接

3最后对于题目中rc的取法,

妈耶,一个一个的解释都深深叨叨的原谅我见识浅薄,

  对于一个二分图,求出其最大匹配。然后取左侧(S侧)所有的未匹配点,按照增广路算法,寻找交错路径(路径上的点都是匹配点),标记掉路径上的所有点(不存在重复标记)。那么左侧(S侧)所有的未标记点,与右侧(T侧)所有的标记点,就是实现最小点覆盖的点

以上是他们的解释,额突然发现我也描述不了,看代码其实比看说的还好懂一些,

对于总数,根据定理,直接跑一个匈牙利就可以求出答案

对于rc的答案,其实是这样的,可以这样想,你现在求r,c输出 以前已经把xy所有的可以相连的点已经连好了,但是此时还有一部分的x没有与y相连(也就是有些石头还没法被哄掉),把这一部分x拿出来,去与y相连,这样连好的y(也就是c列,就是答案的一部分),这样就可以把剩下的用列去打掉,而答案的另一部分就是原来的x(把第二步里面处理的x抠掉以后即是答案)

至于正确性,去看其他人的博客的证明:

点击打开链接    点击打开链接


就这样吧撤退,去操场,程序员爱惜身体

#include <iostream>
#include <cstring>
#include <string>
#include <cstdio>
#include<algorithm>
#include<vector>
#include<set>
#include<map>

using namespace std;

const int maxn=1000+5;
int s[maxn];
int t[maxn];
int lef[maxn];
int righ[maxn];
int eg[maxn][maxn];
int r,c,n;
void init()
{
    memset(eg,0,sizeof(eg));
    memset(lef,0,sizeof(lef));
    memset(righ,0,sizeof(righ));
}
int match(int x)
{
    s[x]=1;
    for(int i=1;i<=c;i++)
    {
        if(eg[x][i]&&!t[i])
        {
            t[i]=1;
            if(!lef[i]||match(lef[i]))
            {
                lef[i]=x;
                return 1;
            }
        }
    }
    return 0;
}
void clearst()
{
    memset(s,0,sizeof(s));
    memset(t,0,sizeof(t));
}
int getans()
{

       int ans=0;
       clearst();
       for(int i=1;i<=r;i++)
       {
           memset(t,0,sizeof(t));
           if(match(i))
             ans++;
       }
      printf("%d",ans);
}
void print()
{
    clearst();
    for(int i=1;i<=c;i++)
    {
        if(lef[i])
        {
            righ[lef[i]]=1;//去除标记的点
        }
    }
    for(int i=1;i<=r;i++)
    {
        if(!righ[i])  //跑
        {
          match(i);
        }
    }
    for(int i=1;i<=r;i++)
        if(!s[i])
        printf(" r%d",i);
    for(int i=1;i<=c;i++)
        if(t[i])
        printf(" c%d",i);
    printf("\n");
}
int main()
{
   while(cin>>r>>c>>n&&r&&c&&n)
   {
       init();
       for(int i=0;i<n;i++)
       {
         int a,b;
         cin>>a>>b;
         eg[a][b]=1;
       }
   //第一步简单匈牙利
   getans();
    //第二步处理
     print();
   }
    return 0;
}
关于博客的引用:1 点击打开链接
2;

;;

;;;


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值