zoukankan      html  css  js  c++  java
  • 【广度优先搜索】ybt1250 The Castle

    http://ybt.ssoier.cn:8088/problem_show.php?pid=1250

    【题目描述】

    一座城堡被分成m*n个方块(m≤50,n≤50),每个方块可有0~4堵墙(0表示无墙)。下面示出了建筑平面图:

    图中的加粗黑线代表墙。几个连通的方块组成房间,房间与房间之间一定是用黑线(墙)隔开的。

    现在要求你编一个程序,解决以下2个问题:

    1、该城堡中有多少个房间?

    2、最大的房间有多大?

    【输入】

    平面图用一个数字表示一个方块(第1个房间用二进制1011表示,0表示无东墙,用十进制11表示)。

    第一行一个整数m(m≤50),表示房子南北方向的长度。

    第二行一个整数n(n≤50),表示房子东西方向的长度。

    后面的m行,每行有n个整数,每个整数都表示平面图对应位置的方块的特征。每个方块中墙的特征由数字P来描述(0≤P≤15)。数字P是下面的可能取的数字之和:

    1(西墙 west)

    2(北墙 north)

    4(东墙 east)

    8(南墙 south)

    室内的墙被定义两次: 例如方块(1,1)中的南墙也被位于其南面的方块(2,1)定义了一次。

    建筑中至少有两个房间。

    【输出】

    第1行:一个整数,表示房间总数;

    第2行:一个整数,表示最大房间的面积(方块数)。

    一道很好的广搜题目,竟然和二进制结合在了一起。

    第一次看到这道题怎么着也是两月以前了。当时一看到这道题我的确不想做,我一开始的思路是既存储墙壁又存储房间(这里指单位房间而不是连成一体的房间),然后根据房间和墙壁的相对位置判断 bfs 是否能执行。但是很明显,一行数组不能拆成两半(一半存储房间,一半存储墙壁),也就是说必须开一个 2M * 2N 的数组,而每次扩展新结点时都要移动两个格子,这无疑给程序设计造成了很大的麻烦。

    时隔两月,我又开始思考这个题目。突然,我灵机一动:为什么非要存储墙壁?只要设计一个结构体表示每个单位房间,然后在这个房间里实现墙壁的存储不就是了!

    构建结构体对象时,根据输入的数字,将一个布尔数组 wall[4] 填充,其中 wall[0]为west、wall[1]为north、wall[2]为east、wall[3]为south。true表示有某个方向有墙壁,false表示没有。

    然后是这奇怪的墙壁表示方法。很有意思,这里采用了二进制。每一个输入的数字都可以看做一个位数不超过 4 的二进制数。只要每次实行位或和位移位操作,就可以知道哪些方向存在墙壁。

    同时还要记录单位房间的位置和是否被访问过。这就是结构体的好处:具有高度的归纳性和整合性。我们不需要再额外地开一个数组来表示某个房间有没有被访问过,从而降低了代码的复杂度(这里的复杂度是指代码的糟乱程度,而不是时(空)间复杂度)。

    然后是构造函数,每次输入时根据要求处理每一个结构体对象。

    struct unit
    {
        int x,y;
        bool wall[4];//1=w,2=n,3=e,4=s;
        bool vis;
        unit()//必要的初始化操作
        {
            x=0,y=0,vis=false;
            memset(wall,false,sizeof(wall));
        }
        unit(int x,int y,int num)
        {
            this->x=x;
            this->y=y;
            for(int i=0;i<4;i++)
            {
                if(!num) wall[i]=false;
                else if(num & 1)//每次实行异或,判断当前位数是否为1或0,分别代表墙壁的存在和不存在。注意要按照指定的西北东南的顺序。
                {
                    wall[i]=true;
                }
                else wall[i]=false;
                num=num>>1;//下一位
            }
        }    
    }a[55][55];//对于数组,如果我们不编写默认构造函数unit(),那么我们就得一个个手动调用unit(x,y,num)函数,否则会发生错误

    然后就可以初始化:

    void init()
    {
        cin>>M>>N;
        int temp;
        for(int i=1;i<=M;i++)
        {
            for(int j=1;j<=N;j++)
            {
                cin>>temp;
                a[i][j]=unit(i,j,temp);//创建新对象的另一个方法
            }
        }
    }

    最后是重中之重的广搜函数。这里需要注意几点:

    1.wall[4]的下标一定要和西北东南对应起来,还有表示扩展结点方向的数组dx[4],dy[4]也要严格按照这样的顺序。也就是说我们不能随便地给dx、dy数组赋值,必须有一定的顺序。比如说往西走是(x+0,y-1),则这一对必须要在下标 0 上。

    2.注意更新最大值和房间量。

    3.判断一个结点是否有效时,除了判断下标范围是否在M、N以内且大于0以外,还要判断一个单位房间是否走过。此时判断“是否存在墙壁”使用当前对象下标(也就是刚从队列里pop掉的对象),而判断有没有走过则是使用当前对象扩展的下一个对象的下标。

    不理解可以看代码:

    int M,N;
    int dx[4]={0,-1,0,1};//注意点1
    int dy[4]={-1,0,1,0};
    int maxsize=-1;
    int count;
    void bfs(int x,int y)
    {
        if(a[x][y].vis) return;
        int Size=1;
        queue<unit> q;
        q.push(a[x][y]);
        a[x][y].vis=true;
        while(!q.empty())
        {
            unit u=q.front();
            q.pop();
            for(int i=0;i<4;i++)
            {
                int nx=u.x+dx[i],ny=u.y+dy[i];
                if(nx>=1 && nx<=M && ny>=1 && ny<=N && (!u.wall[i]) && (!a[nx][ny].vis))//注意3:不是“!u.vis”!
                {
                    q.push(a[nx][ny]);
                    a[nx][ny].vis=true;
                    Size++;
                } 
            }
        }
        maxsize=max(Size,maxsize);//注意点2
        count++;
    }

    AC代码:

    #include<iostream>
    #include<cstring>
    #include<queue>
    using namespace std;
    int M,N,dx[4]={0,-1,0,1},dy[4]={-1,0,1,0},maxsize=-1,count;
    struct unit
    {
        int x,y;
        bool wall[4];//1=w,2=n,3=e,4=s;
        bool vis;
        unit()
        {
            x=0,y=0,vis=false;
            memset(wall,false,sizeof(wall));
        }
        unit(int x,int y,int num)
        {
            this->x=x;
            this->y=y;
            for(int i=0;i<4;i++)
            {
                if(!num) wall[i]=false;
                else if(num & 1)
                {
                    wall[i]=true;
                }
                else wall[i]=false;
                num=num>>1;
            }
        }    
    }a[55][55];
    void init()
    {
        cin>>M>>N;
        int temp;
        for(int i=1;i<=M;i++)
            for(int j=1;j<=N;j++)
            {
                cin>>temp;
                a[i][j]=unit(i,j,temp);
            }
    }
    void bfs(int x,int y)
    {
        if(a[x][y].vis) return;
        int Size=1;
        queue<unit> q;
        q.push(a[x][y]);
        a[x][y].vis=true;
        while(!q.empty())
        {
            unit u=q.front();
            q.pop();
            for(int i=0;i<4;i++)
            {
                int nx=u.x+dx[i],ny=u.y+dy[i];
                if(nx>=1 && nx<=M && ny>=1 && ny<=N && (!u.wall[i]) && (!a[nx][ny].vis))
                {
                    q.push(a[nx][ny]);
                    a[nx][ny].vis=true;
                    Size++;
                } 
            }
        }
        maxsize=max(Size,maxsize);
        count++;
    }
    int main()
    {
        init();
        for(int i=1;i<=M;i++)
            for(int j=1;j<=N;j++)
                bfs(i,j);
        cout<<count<<endl<<maxsize;
        return 0;
    }
  • 相关阅读:
    微访谈之1:解答各位朋友关心的问题
    深入浅出SQL Server中的死锁(实战篇)
    怎样玩转千万级别的数据
    Another MySQL daemon already running with the same unix socket
    c++ undefined reference to mysqlinit
    Another MySQL daemon already running with the same unix socket
    linxu 挂载分区
    C# RSA
    谷歌地图实现车辆轨迹移动播放(google map api)
    百度地图实现车辆轨迹移动播放(baidu map api)
  • 原文地址:https://www.cnblogs.com/jiangyuechen/p/12896004.html
Copyright © 2011-2022 走看看