ZOJ 1654 Place the Robots(超牛的建图思路) - from lanshui_Yang

Place the Robots

Time Limit: 5 Seconds Memory Limit: 32768 KB

Robert is a famous engineer. One day he was given a task by his boss. The background of the task was the following:

Given a map consisting of square blocks. There were three kinds of blocks: Wall, Grass, and Empty. His boss wanted to place as many robots as possible in the map. Each robot held a laser weapon which could shoot to four directions (north, east, south, west) simultaneously. A robot had to stay at the block where it was initially placed all the time and to keep firing all the time. The laser beams certainly could pass the grid of Grass, but could not pass the grid of Wall. A robot could only be placed in an Empty block. Surely the boss would not want to see one robot hurting another. In other words, two robots must not be placed in one line (horizontally or vertically) unless there is a Wall between them.

Now that you are such a smart programmer and one of Robert's best friends, He is asking you to help him solving this problem. That is, given the description of a map, compute the maximum number of robots that can be placed in the map.


Input


The first line contains an integer T (<= 11) which is the number of test cases.

For each test case, the first line contains two integers m and n (1<= m, n <=50) which are the row and column sizes of the map. Then m lines follow, each contains n characters of '#', '*', or 'o' which represent Wall, Grass, and Empty, respectively.


Output

For each test case, first output the case number in one line, in the format: "Case :id" where id is the test case number, counting from 1. In the second line just output the maximum number of robots that can be placed in that map.


Sample Input

2
4 4
o***
*###
oo#o
***o
4 4
#ooo
o#oo
oo#o
***#


Sample Output

Case :1
3
Case :2
5

       

       题目大意:给定一个 m*n 的地图,地图有方格组成,在地图中有三种方格——墙、草地和空地。要求在地图中放置尽可能多的机器人,每个机器人都配备了激光枪,可以同时向4个方向(上、下、左、右)开枪。机器人一直待在最初始放置的方格处,不可移动,然后一直向四个方向开枪。激光枪发射的激光可以穿透草地,但不能穿透墙壁。机器人只能放置在空地,并且不能使机器人相互攻击。输出可以放置的机器人的最大数目。‘o’表示空地,‘#’表示墙,‘*’表示草地。

       解题思路:此题不太容易想,关键是如何把此题转换成求解二部图最大匹配的问题,也就是如何建图。

      下面具体阐述:将每一行被墙隔开且包含空地的连续区域称作”块“,显然,在一个块中,最多只能放置一个机器人。把这些块编上号,注意:在同一行中,如果两个空地之间没有墙壁,只有草地,那么这两个空地应属于“同一块”。同样,把竖直方向的块也编上号。然后把每个横向块看作二部图中顶点集合X中的顶点,竖向块看作集合Y中的顶点,若两个块之间有公共的空地(注意,每两个块最多有一个公共空地),则在它们之间连边。由于每条边表示一个空地,有冲突的空地之间必有公共顶点,所以问题转化为在二部图中找没有公共顶点的最大边集,也就是求最大匹配问题。

        请看代码:

#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std ;
const int MAXN = 55 ;
int n , m ;
char map[MAXN][MAXN] ;
short g[MAXN*MAXN][MAXN*MAXN] ; // 数组一定不要开小,否则可能直接WA !!
                                // 注意使用short或bool ,避免MLE
int cx[MAXN * MAXN] ;
int cy[MAXN * MAXN] ;
struct Point
{
    int br ;
    int bc ;
    Point():br(-1) , bc(-1) {}
} s[MAXN * MAXN] ;
int ha[MAXN][MAXN] ;     // 辅助数组
bool vis[MAXN * MAXN] ;  // 标记数组
int sumbr = -1 ;
int sumbc = -1 ;
void init ()
{
    memset(ha , -1 , sizeof(ha)) ;
    memset(g , 0 , sizeof(g)) ;
    memset(cx , -1 , sizeof(cx)) ;
    memset(cy , -1 , sizeof(cy)) ;
    sumbr = -1 ;   // 记录横向块数
    sumbc = -1 ;   //记录竖向块数
    scanf("%d%d\n" , &n , &m) ;
    int i , j ;
    for(i = 0 ; i < n ; i ++)
    {
        scanf("%s" , map[i]) ;
    }
    int cnt = 0 ;
    int first = -1 ;
    for(i = 0 ; i < n ; i ++)  // 给横向块标号,从0开始
    {
        first = -1 ;
        for(j = 0 ; j < m ; j ++)
        {
            if(map[i][j] == '#')
            {
                first = -1 ;
            }
            if(map[i][j] == 'o')
            {
                if(first == -1)
                {
                    sumbr ++ ;
                    first = 1 ;
                }
                s[cnt].br = sumbr ;
                ha[i][j] = cnt ;
                cnt ++ ;
            }
        }
    }
    for(j = 0 ; j < m ; j ++)  // 给竖向块标号,从0开始
    {
        first = -1 ;
        for(i = 0 ; i < n ; i ++)
        {
            if(map[i][j] == '#')
            {
                first = -1 ;
            }
            if(map[i][j] == 'o')
            {
                if(first == -1)
                {
                    sumbc ++ ;
                    first = 1 ;
                }
                int tmp = ha[i][j] ;
                s[tmp].bc = sumbc ;
            }
        }
    }
    for(i = 0 ; i < cnt ; i ++)  // 建图
    {
        g[ s[i].br ][ s[i].bc ] = 1 ;
    }
}
int ca = 0 ;
int path(int v)  // 匈牙利算法
{
    int i ;
    for(i = 0 ; i <= sumbc ; i ++)
    {
        if(g[v][i] == 1 && !vis[i])
        {
            vis[i] = true ;
            if(cy[i] == -1 || path(cy[i]))
            {
                cy[i] = v ;
                cx[v] = i ;
                return 1 ;
            }
        }
    }
    return 0 ;
}
void solve()
{
    int i ;
    int ans = 0 ;
    for(i = 0 ; i <= sumbr ; i ++)
    {
        if(cx[i] == -1)
        {
            memset(vis , 0 , sizeof(vis)) ;
            if(path(i))
                ans ++ ;
        }
    }
    printf("Case :%d\n" , ++ ca) ;
    printf("%d\n" , ans) ;
}
int main()
{
    int T ;
    scanf("%d" , &T) ;
    while (T --)
    {
        init() ;
        solve() ;
    }
    return 0 ;
}


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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值