hdu 1045 Fire Net&二分图解法

Fire Net

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1045

之前做了几个二分图的题目,以为二分图仅此而已了,今天经过某王的提醒才知道二分图题目的难点在于如何建图。其实之前听华理某队长介绍时就得知图论题目大多是套模板,但是难就难在你不知道这是一道图论题,当时也没太在意。遥想上海邀请赛,似乎有这道题目的原题再现,没有A掉甚是可惜。这道老题目就是这样,大一的时候记得我A过这道题目,当时是结合网上的思路暴力+dfs解决的,可以注意一下网上有一个暴力dfs的代码是错的,不能过某一个sample input,但是能ac,是hdu的数据比较弱吧,思路不是很清晰。如今学完二分图再来仔细看了下这道题目,不就是二分图最大匹配吗!

题目大意:




有一个Map[N][N],空白方格上放置墙壁(黑方格),炮台(黑色圆),要求在已知墙壁的情况下放置最多的炮台(最大匹配),限制条件:同一行同一列只能放置一个炮台。

题目思路:建图+二分图最大匹配

一开始的思考:

一开始没能理解,以为是每一个格子作为X,在将每一个格子作Y,如果有墙壁,则Map[X][Y]=-1,显然不可以,因为4X4的Map在第三个样例中可以达到5,因为有墙壁的作用

正确建图方法:

横竖分区。先看每一列,同一列相连的空地同时看成一个点,显然这样的区域不能够同时放两个点。这些点作为二分图的X部。同理在对所有的行用相同的方法缩点,作为Y部。连边的条件是两个区域有相交部分。最后求最大匹配就是答案。

为什么是这么建图呢?大家可以思考一下,其实是没有问题的。

以列连通块为点存储在a[N][N]:

1 0 2 3
1 4 2 3
0 0 2 3
5 6 2 3
以列连通块为点存储在b[N][N]:
1 0 2 2
3 3 3 3
0 0 4 4
5 5 5 5
这样左点集中gm=6,右点集中gn=5。

连边方法:

for (int i = 1; i <= ncase; i++)
<span style="white-space:pre">	</span>for (int j = 1; j <= ncase; j++)
             if (map[i][j] == '.')
             mat[a[i][j]][b[i][j]] = mat[b[i][j]][a[i][j]] = 1;
这样建图过程就结束了,剩下的就是二分图模板了,之前博客中已经介绍了我的模板,在此不做解释了。

可以看出思路很清晰,比暴力DFS省去很多功夫,总结就是要发现这是一道二分图的题目。

最后贴上AC代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#define N 20
int useif[N];   //记录y中节点是否使用 0表示没有访问过,1为访问过
int link[N];   //记录当前与y节点相连的x的节点
int mat[N][N]; //记录连接x和y的边,如果i和j之间有边则为1,否则为0
int gn,gm;    //二分图中x和y中点的数目
bool can(int t)
{
    int i;
    for(i=1;i<=gm;i++)
    {
       if(useif[i]==false && mat[t][i])
       {
           useif[i]=true;
           if(link[i]==-1 || can(link[i]))
           {
              link[i]=t;
              return true;
           }
       }
    }
    return false;
}
int MaxMatch()
{
    int i,num;
    num=0;
    memset(link,0xff,sizeof(link));
    for(i=1;i<=gn;i++)
    {
      memset(useif,0,sizeof(useif));
       if(can(i)) num++;
    }
    return num;
}
int main()
{
    int ncase;
    char map[N][N];
    int a[N][N],b[N][N];
    while(scanf("%d",&ncase)==1)
    {
        if(ncase==0)
            break;
        for(int i=0;i<ncase;i++)//map[][]存储图
            scanf("%s",map[i+1]+1);
        memset(mat,0,sizeof(mat));//初始化二分图
        memset(a,-1,sizeof(a));//列连通块&左图
        memset(b,-1,sizeof(b));//行连通块&右图
        gm=gn=1;
        for (int i = 1; i <= ncase; i++)
            for (int j = 1; j <= ncase; j++)
                if (map[i][j] == '.' && a[i][j] == -1)
                {
                    for (int k = i; map[k][j] == '.'&& k <= ncase; k++)
                        a[k][j] = gm;
                    gm++;
                }//左点集
        for (int i = 1; i <= ncase; i++)
            for (int j = 1; j <= ncase; j++)
                if (map[i][j] == '.' && b[i][j] == -1)
                {
                    for (int k = j; map[i][k] == '.'&& k <= ncase; k++)
                    b[i][k] = gn;
                    gn++;
                }//右点集
        for (int i = 1; i <= ncase; i++)
            for (int j = 1; j <= ncase; j++)
                if (map[i][j] == '.')
                    mat[a[i][j]][b[i][j]] = mat[b[i][j]][a[i][j]] = 1;//连边
        gm--;gn--;//注意最后要减去1,因为前面多加了一次
        printf("%d\n",MaxMatch());
    }
}

解决了当年留下的问题心里还是很高兴的,图论远不止我想的那么简单。一天又过去了,今天过的怎么样,梦想是不是更远了?



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值