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());
}
}
解决了当年留下的问题心里还是很高兴的,图论远不止我想的那么简单。一天又过去了,今天过的怎么样,梦想是不是更远了?