uva 11419最小点覆盖和…

大概题意:在一个R*C大小的网格中,网格上面放着一些目标,可以在网格外面发射子弹,子弹可以沿着垂直或则水平的方向射出,并打掉该路径上的所有目标,问最少需要多少子弹,并把这些子弹的位置输出。

解法:每发射一个子弹,我们就能把某一行或者某一列的目标都清楚掉,因此我们每一行、每一列看成一个目标,并把这一行、列中的目标都标同一个号,那么我们就能通过同一个位置的目标来得到这个位置所在行、列的关系(在它们之间连一条边),我们要求的就转化为,用最少的点去覆盖所有的边,也就是最小点覆盖 , 也就是这个二分图的最大匹配。

子弹的位置:首先我们要知道 , 对于二分匹配算法 , 一定会遍历所有边 , 也就是说所有边都会属于一个或多个匈牙利树 。我们先把这些边分为匹配边和非匹配边 。 
    我们先来确定非匹配边的情况,对于一条非匹配边,我们可以确定这条边肯定有一个点或两个点都属于匹配点 , 对于只有一个点属于匹配点 ,那么我们肯定要选这个匹配点 , 因此 , 我们只需要在求出最大匹配之后 , 再用X集合中的非匹配点去发展匈牙利树 ,然后我们标记X集合和Y集合中已经走过的点 , 最后 X 中没有标记的点和 Y 中已经标记的点 , 就是子弹的位置 。由于我们是用非匹配点去发展匈牙利树 , 因此如果存在边 , 那么我们就一定要选这条边的另外一个点 , 并且这个点一定是匹配点 ,那么和这个点相连的所有边就已经都覆盖了 , 再往后面的匈牙利树 , 我们就能很容易得出了。
    对于匹配边 , 在我们后面已经得到的匈牙利树中 ,如果匹配边已经在这个树种 , 那么我们可以确定这条边肯定已经被覆盖 , 对于不在匈牙利树中的匹配边 ,我们可以确定这条匹配边上的X集合中的点 , 肯定没有被标记 , 那么根据上面的输出规则 , 我们可以确定用这中方法得到的点 ,肯定已经覆盖了所有边。


代码:


#define MAXN 1010
#define INF 1000000
#define max(x , y) (x)>(y)?(x):(y)
#define min(x , y) (x)<(y)?(x):(y)
int n , m , k; //  记录每条边的权值
int pre[MAXN] ;  // 记录和y中点匹配的点是哪个点
vectorgrap[MAXN];
int wr[MAXN] , wc[MAXN];
int cx[MAXN] , cy[MAXN];
int bzx[MAXN] , bzy[MAXN];
void init()
{
memset(wr , 0 , sizeof(wr));
memset(wc , 0 , sizeof(wc));
    for(int i = 1; i<= n; i++)
    {
       grap[i].clear();
    }
    memset(cx , -1 ,sizeof(cx));
    memset(cy , -1 ,sizeof(cy));
}
bool match(int i)  // 寻找增广路
{
    bzx[i] = 1;
    for(int j = 0; j< grap[i].size(); j++)
    {
       int v = grap[i][j];
       if(!pre[v])
       {
           pre[v] =1;
bzy[v] = 1;
           if(cy[v]== -1 || match(cy[v]))
           {
              cx[i] = v;
              cy[v] = i;
              return true;
           }
       }
   
    }
    return false;
}
int km()
{
    int i ;
    memset(bzx , 0 ,sizeof(bzx));
memset(bzy , 0 , sizeof(bzy));
    int res = 0;
    for(i = 1; i<= n; i++)
    {
       if(cx[i] == -1&& wr[i])
       {
           memset(pre, 0 , sizeof(pre));
          if(match(i))  res += 1;
       }
    }
    return res;
}
int main()
{
    while(scanf("%d %d %d" ,&n , &m , &k) !=EOF)
    {
       if(n+m+k == 0)  break;
       init();
       int i , x , y ;
       for(i = 1; i <= k; i++)
{
scanf("%d %d" , &x ,&y);
grap[x].push_back(y);
wr[x] = wc[y] = 1;
}
       x = km();
km();
      
       printf("%d" , x);
for(i = 1; i <= n; i++)
if(!bzx[i] && wr[i]) printf(" r%d" , i);
for(i = 1; i <= m; i++)
if(bzy[i] && wc[i]) printf(" c%d" , i);
cout<<endl;
    }
    return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值