UVA11419[SAM I AM] 二分图最小覆盖模型

二分图最小点覆盖模型

1.什么是最小点覆盖。

一条边如果至少有一个顶点被选中,那么就定义这条边就被覆盖。最小点覆盖,就是求最少选中多少个点使得二分图中所有边都被覆盖。

2.怎么求最小点覆盖。

这里写图片描述

上面左边的图 经过二分图匹配 得出了{ (1,7), (2,5), (4.8) } 三条匹配边
我们从右边的 未盖点(不是匹配点的点) 开始 按照 匹配边->非匹配边->匹配边…->匹配边 的顺序(以匹配边结尾)访问,把每一个访问过的结点都打上标记, 得到了下图:

这里写图片描述

图中粉红色为访问过的结点,黄色为访问的路线。

最小点覆盖=左边访问过的点+右边未访问过的点,即 2,4,7

最小点覆盖数=最大匹配数

3.证明

(令S为左边访问过的点,和右边没访问过的点的集合)
(令P为最大匹配集合)

(1) 为什么 |S|==|P| (最小匹配数=最小覆盖数)

因为根据定义,先走 未匹配边, 再走匹配边, 所以左边访问到的一定是匹配点, 可以代表匹配边,因为往右走时,走匹配边, 所以,如果访问过左边的匹配点, 那么一定能访问 该匹配边上的 右边的匹配点, 所以不会重复计算。 因为右边的匹配点不会作为起点而每被访问的非匹配点一定会作为起点而被访问,所以 如果右边有点没有被访问, 那一定就是没有被访问到的匹配边的匹配点。所以最小匹配数=左边被访问过的点数+右边没有被访问过的点数。

(2)为什么S能够覆盖所有边。

初步看来在这个二分图中,边分 3 种:
…..1…..左边被标记
…..2…..右边没被标记
…..3…..左边没被标记 并且 右边被标记
进一步来看,第三种边不存在,原因如下:如果该边为匹配边,那么左右两边要么都被访问,要么都不被访问。如果该边不为匹配边,有两种情况:一,右边端点为起点,那左端点会被访问到。二,右端点不为起点,那么右端点只能为非匹配点 ( 因为如果右端点为匹配点,要么它不会被访问,不符合对这条边的定义。要么它被作为一条匹配边的端点访问,那么这条边会被作为非匹配边访问到左边结点,不符合对这条边定义), 如果左端点为匹配点,那一定会被访问,否则不符合最大匹配定义。综上,第三条边不存在。
所以只有1,2两种边,能够被 左边访问过+右边没访问过 结点数包含

(3)为什么S是最小的

因为|S|==|P| 所以如果S减小了,那么二分图最大匹配数就不是最大的了。

得证


题目链接


题意:给你一个n*m的矩阵,上面有一些格子上有目标,我们可以在格子的外面用枪打目标,一发子弹可以消灭一行或者一列目标,问你最少多少枪能把目标打光,并且输出开枪的位置。


solution:把行放在S集合,列放在T集合,每一个目标的横纵坐标建一条边,二分图最大匹配。

#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1005;
int w[N][N];
int line[N], S[N], T[N], rm[N];
int n, m, k;
bool find(int x){
    S[x]=1;
    for ( int y=1; y<=m; y++ ){
        if( !T[y] && w[x][y] ){
            T[y]=1;
            if( !line[y] || find( line[y] ) ){
                line[y]=x;
                rm[x]=y;
                return true;
            } 
        }
    }
    return false;
}  

int Hungarian(){
    int ans=0;
    memset(line,0,sizeof(line));
    memset(rm,0,sizeof(rm));
    for ( int i=1; i<=n; i++ ){
        memset(S,0,sizeof(S));
        memset(T,0,sizeof(T));
        if( find( i ) )ans++; 
    } 
    return ans;
}

int main(){
    while( scanf("%d%d%d", &n, &m, &k )!=EOF && n ){
        memset(w,0,sizeof(w));
        for ( int i=1; i<=k; i++ ){
            int x, y;
            scanf("%d%d", &x, &y );
            w[x][y]=1;
        }
        int ans=Hungarian();
        printf("%d", ans);
        memset(S,0,sizeof(S));
        memset(T,0,sizeof(T));
        for ( int i=1; i<=n; i++ ) if( !rm[i] ) find(i);
        vector<int> A,B; 
        for ( int i=1; i<=n; i++ ) if( !S[i] ) A.push_back(i);
        for ( int i=1; i<=m; i++ ) if( T[i] ) B.push_back(i);

        for ( int i=0; i<A.size(); i++ ) printf(" r%d", A[i] );
        for ( int i=0; i<B.size(); i++ ) printf(" c%d", B[i] );
        puts(""); 
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值