题目链接: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()); } }