【最大独立集 && 有墙 && 无向图】HDU - 1045 Fire Net

Problem Description

给你一个正方形棋盘。每个棋子可以直线攻击,除非隔着石头。现在要求所有棋子都不互相攻击,问最多可以放多少个棋子
这里写图片描述
如上述图,图1是棋盘,图2是放的最多的

思路:

可以参考博客先A了ZOJ 1654。这两题是一样的题,上述博客讲的太好了。

核心:如果把最大独立集,点的关系转换到二分图上。
本题也是二分图匹配的一个经典题目。我们将每一行,每一列被墙隔开,且包含空地的连续区域称作“块”。显然,在一个块之中,最多只能放一个机器人,我们把这些水平方向的块编上号。同样,把竖直方向的块也编上号
把每个横向块看作X部的点,竖向块看作Y部的点,若两个块有公共的空地(代表这两个块有联系,这个联系就把最大独立集,点关系转换到了二分图上),则在它们之间连边。于是,问题转化成这样的一个二分图
相当于每一个可以放棋子的点,就会有一个联系,水平方向的块,和竖直方向的块。也就是二分图,接下来就是求二分图最大匹配。

#include<bits/stdc++.h>
using namespace std;
struct node
{
    int to, next;
};
char s[55][55];
int sx[55][55], sy[55][55], nx, ny;
node Map[20000];
int head[500], vis[500], match[500], cnt;
bool dfs(int u)
{
    for(int i = head[u]; ~i; i = Map[i].next)
    {
        int to = Map[i].to;
        if(!vis[to])
        {
            vis[to] = 1;
            if(!match[to] || dfs(match[to]))
            {
                match[to] = u;
                match[u] = to;
                return 1;
            }
        }
    }
    return 0;
}
int solve()
{
    int ans = 0;
    memset(match, 0, sizeof(match));
    for(int i = 1; i <= nx; i++)
    {
        if(!match[i])
        {
            memset(vis, 0, sizeof(vis));
            if(dfs(i)) ans++;
        }
    }
    return ans;
}
void add(int u, int v)
{
    Map[cnt].to = v;
    Map[cnt].next = head[u];
    head[u] = cnt++;
}
int main()
{
    int n, i, j;
    while(~scanf("%d", &n) && n)
    {
        for(i = 0; i < n; i++) scanf("%s", s[i]);
        nx = ny = 0;//有多少个水平方向的块,竖直方向的块
        memset(sx, 0, sizeof(sx));//初始化,为了求水平块和竖直块的联系
        memset(sy, 0, sizeof(sy));

        for(i = 0; i < n; i++)//水平块
        {
            int flag = 0;
            for(j = 0; j < n; j++)
            {
                if(s[i][j] == '.')
                {
                    if(!flag) nx++;
                    flag++;
                    sx[i][j] = nx;//该点属于哪个块
                }
                else if(s[i][j] == 'X') flag = 0;
            }
        }
        for(i = 0; i < n; i++)//竖直块
        {
            int flag = 0;
            for(j = 0; j < n; j++)
            {
                if(s[j][i] == '.')
                {
                    if(!flag) ny++;
                    flag++;
                    sy[j][i] = ny;//该点属于那个块
                }
                else if(s[j][i] == 'X') flag = 0;
            }
        }
        cnt = 0;
        memset(head, -1, sizeof(head));
        for(i = 0; i < n; i++)//建二分图
        {
            for(j = 0; j < n; j++)
            {
                if(sx[i][j] && sy[i][j])//水平,竖直的块有公共部分
                {
                    add(sx[i][j], sy[i][j]+nx);//+nx是为了防止点重复
                    add(sy[i][j]+nx, sx[i][j]);
                }
            }
        }
        printf("%d\n", solve());//求二分图最大匹配
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值