Fire Net 【HDU 1045】
这道题被归在匹配问题里面我就老老实实去用二分匹配图稿它了,这道题是道匈牙利算法,匈牙利那部分没有难度,难就难在建图(真的难想,反正我是没想到),这个建图运用了“缩图法”, 给图中每个点分配一个它所在的“行区域”和“列区域”,并以它的行区域和列区域编号分别代表它的行坐标和列坐标。
从图中可知(图画的确实挺丑的,逃),在该行相邻格子连通的格子组成一个行区域,同理可以定义列区域。
!!现在是重点,是建图的核心思想:若一个行区域和一个列区域相交,设这个相交区域为U,若U内有n个点,则这n个点中只能有一个点可以放炮台,也就是说U内的点是可以相互连通的,其中任意一个点可以发射炮弹到U中其他所有点。所以这道题就可以转化为求出这样的U区域最多有多少个。
根据二分图的定义,再来看一看这个图,所有的列区域互不相交,所有的行区域也是互不相交,可架设炮台的空地必为一个行区域和一个列区域交集中的一个元素,要求是求出行区域和列区域的交集最多有多少个。那么我们就可以把这个图看作一个二分图G,行区域集合和列区域集合分别看作点集U,V, 每个空地的行区域编号和列区域编号之间可以连线,这道题就实质上是一个二分图最大匹配问题,用匈牙利算法就可以轻松将其解决。
下面是代码:
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cstdlib>
#define RE(a,x) memset(a, x, sizeof(a))
using namespace std;
const int maxn = 10;
char mp[maxn][maxn]; //存图
bool link[maxn][maxn]; //记录某个行区域和列区域是否相交
bool vis[maxn]; //记录这个列区域有没有被访问过
int ma[maxn], mma[maxn]; //分别记录列区域匹配的是哪个行区域、行区域匹配的是哪个列区域
int row[maxn][maxn], col[maxn][maxn]; //存储原图中点所在的行区域编号、列区域编号
int n, rcnt, ccnt; //矩阵规模、行区域编号、列区域编号
bool judge(int x)
{
for(int i = 0; i < ccnt; i++)
{
if(!vis[i] && link[x][i])
{
vis[i] = true;
if(ma[i] == -1 || judge(ma[i]))
{
ma[i] = x;
mma[x] = i;
return true;
}
}
}
return false;
}
int getans() //给行区域匹配列区域,反之亦可
{
RE(ma, -1);
RE(mma, -1);
int sum = 0;
for(int i = 0; i < rcnt; i++)
{
if(mma[i] == -1) {
RE(vis, false);
if(judge(i))
sum++;
}
}
return sum;
}
int main()
{
freopen("input.txt","r",stdin);
while(scanf("%d", &n), n) {
RE(row, -1); RE(col, -1); RE(link, false);
for(int i = 0; i < n; i++) {
scanf("%s", mp[i]);
}
rcnt = 0, ccnt = 0;
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
if(mp[i][j] == '.' && row[i][j] == -1)
{
for(int k = j; mp[i][k] == '.' && k < n; ++k)
row[i][k] = rcnt;
++rcnt;
}
if(mp[j][i] == '.' && col[j][i] == -1)
{
for(int k = j; mp[k][i] == '.' && k < n; ++k)
col[k][i] = ccnt;
++ccnt;
}
}
}
for(int i = 0; i < n; i++)
{
for(int j = 0; j < n; j++)
{
if(mp[i][j] == '.') link[row[i][j]][col[i][j]] = true;
}
}
printf("%d\n", getans());
}
system("pause");
}