uva11419——SAM I AM——————【最小覆盖数、打印最小覆盖】

/**

   解题思路:将每一行看作一个X结点,每一列看作一个Y结点,每个目标对应一条边,这样,子弹打掉所有目标意味着每条边至少有一个结点被选中。构造解:借助于匈牙利树,从X中的所有未盖点出发扩展匈牙利树,标记树中的所有点,则X中的未标记点喝Y中的已标记点组成所求的最小覆盖。

*/

题目大意:在R*C的网格中放目标,子弹水平或垂直飞行,会打掉飞行路线上所有目标,问打掉所有目标,至少需要多少子弹。各从什么位置发射。

Konig定理:

二分图的最小顶点覆盖数等于最大匹配数。

 证明:

 为主便叙述,假设G分为左边X和右边Y两个互不相交的点集。。。。。。

假设G经过匈牙利算法后找到一个最大匹配M,则可知G中再也找不到一条增广路径。

标记右边未匹配边的顶点,并从右边未匹配边的顶点出发,按照边:未匹配->匹配->未匹配...,的原则标记途中经过的顶点,则最后一条经过的边必定为匹配边。重复上述过程,直到右边不再含有未匹配边的点。

记得到的左边已标记的点和右边未标记的点为S, 以下证明S即为所求的最小顶点集。

1| S | == M 
    显然,左边标记的点全都为匹配边的顶点,右边未标记的点也为匹配边的顶点。因此,我们得到的点与匹配边一一对应。

2S能覆盖G中所有的边。

       上途S中点所得到的边有以下几种情况:

       1)左右均标记;

       2)左右均无标记;

       3)左边标记,右边未标记;

       若存在一条边e不属于S所覆盖的边集,则e 左边未标记右边标记。

如果e不属于匹配边,那么左端点就可以通过这条边到达(从而得到标记);如果e属于匹配边,那么右端点不可能是一条路径的起点,于是它的标记只能是从这条边的左端点过来的左端点就应该有标记。

 

3S是最小的覆盖。

       因为要覆盖这M条匹配边至少就需要M个点。

/**
    最小覆盖:选择尽量少的点,使得每条边至少有一个端点被选中
    最小覆盖数等于最大匹配数。
    
*/

#include<iostream>
#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAXV=1100;            //v_t_l标记匈牙利树的X面的结点
bool G[MAXV][MAXV],vis[MAXV],vis_tree_l[MAXV],vis_tree_r[MAXV];
int match[MAXV],coving[MAXV];   //coving筛选出X面的未盖点
int R,C,N;
bool find_AP(int u){

    for(int i=1;i<=C;i++){

        if(!vis[i]&&G[u][i]){

            vis[i]=true;
            if(!match[i]||find_AP(match[i])){

                match[i]=u;
                return true;
            }
        }
    }
    return false;
}
void find_hungarytree(int u){

    vis_tree_l[u]=1;                //标记X面的结点
    for(int i=1;i<=C;i++){
            
        if(!vis[i]&&G[u][i]){

            vis[i]=true;
            vis_tree_r[i]=1;        //标记Y面的结点
            find_hungarytree(match[i]);//递归Yi对应的匹配点match[i]
        }
    }
}
int main(){

    while(scanf("%d%d%d",&R,&C,&N)!=EOF&&(R+C+N)){

        memset(G,0,sizeof(G));
        memset(match,0,sizeof(match));
        memset(vis_tree_l,0,sizeof(vis_tree_l));
        memset(vis_tree_r,0,sizeof(vis_tree_r));
        int a,b,ans=0;
        int rc=R<C?C:R;
        for(int i=1;i<=rc;i++){

            coving[i]=i;                //初始化未盖点数组
        }
        for(int i=0;i<N;i++){

            scanf("%d%d",&a,&b);
            G[a][b]=true;
        }
        for(int i=1;i<=R;i++){

            memset(vis,0,sizeof(vis));
            if(find_AP(i))
                ans++;
        }
        printf("%d",ans);
        for(int j=1;j<=C;j++){

            if(match[j])                //如果是匹配点
               coving[match[j]]=0;      //表示为匹配点
        }
        for(int i=1;i<=R;i++){

            if(!coving[i])              //过滤掉匹配点
                continue;
            memset(vis,0,sizeof(vis));
            find_hungarytree(i);
        } /*根据从X面未盖点扩展匈牙利树,X面中未标记点和Y面
     中已标记点集合为最小覆盖,求出最小覆盖点集*/
        for(int i=1;i<=R;i++){         

            if(vis_tree_l[i]==0){
                printf(" r%d",i);
            }
        }
        for(int i=1;i<=C;i++){

            if(vis_tree_r[i]==1){

                printf(" c%d",i);
            }
        }
        printf("\n");
    }
    return 0;
}

/*
4 4 3
1 1
1 4
3 2

4 4 2
1 1
2 2

4 4 7
1 1
1 2
1 3
1 4
2 1
3 1
4 1

4 5 8
1 3
2 1
2 2
2 4
3 3
4 3
4 4
4 5



3 3 5
1 1
1 2
2 1
2 2
3 2

*/



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值