Fire Net HDU 1045(缩图+二分图最大匹配)

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");
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值