USACO_CHA2_城堡

一、题目描述

链接
ACwing: https://www.acwing.com/problem/content/solution/1361/1/
牛客: https://ac.nowcoder.com/acm/contest/400/A

农夫约翰购买的彩票中了大奖,这让他赢得了一座坐落于爱尔兰乡村的如同神话一般的城堡!

约翰想要将关于城堡的一切统统告诉奶牛,让它们一起分享他的快乐。

在这之前,他需要知道城堡中共有多少个房间,最大的房间有多大。

此外,他还想拆掉一堵墙从而腾出一个更大的房间来。

城堡的平面图可以被划分为 N×M 个方格区域,每个方格区域可以有0~4面墙。

当然,城堡的外边缘一定都是墙壁,从而防止风吹雨打。

参考一下下面这个带注释的城堡平面图:

     1   2   3   4   5   6   7
   #############################
 1 #   |   #   |   #   |   |   #
   #####---#####---#---#####---#   
 2 #   #   |   #   #   #   #   #
   #---#####---#####---#####---#
 3 #   |   |   #   #   #   #   #   
   #---#########---#####---#---#
 4 # ->#   |   |   |   |   #   #   
   ############################# 

#  = 墙壁     -,|  = 没有墙壁
-> = 将箭头指向的这面墙拆除就可以得到最大的房间

注意:地图方向为:上北下南左西右东。

如图所示,这个城堡是由 4×7 个方格区域构成,相连的方格区域构成一个大的房间,该平面图中共包含 5 个房间(大小分别为 9、7、3、1、8)。

将箭头所指的那面墙移除之后,两个房间之间被打通,共同组成了一个新的最大的房间,

城堡保证至少拥有两个房间,一定拥有可以被拆掉的墙。

请你帮忙计算一下,城堡中共有多少个房间,最大的房间的面积是多少,通过移除一面墙可以获得的房间的最大面积是多少,移除哪面墙可以获得面积最大的房间。

输入格式
第一行包含两个整数 M,N,分别表示城堡东西方向的长度和南北方向的长度。

接下来 N 行,每行包含 M 个整数,每个整数都表示平面图对应位置的方块的墙的特征。

每个方块中墙的特征由数字 P 来描述,我们用1表示西墙,2表示北墙,4表示东墙,8表示南墙,P 为该方块包含墙的数字之和。

例如,如果一个方块的 P 为3,则 3 = 1 + 2,该方块包含西墙和北墙。

城堡的内墙被计算两次,方块(1,1)的南墙同时也是方块(2,1)的北墙。

输出格式
输出共包含四行。

第一行输出房间总数。

第二行输出最大房间面积。

第三行输出移除一面墙可以获得的房间的最大面积。

第四行输出移除哪面墙可以获得面积最大的房间,形容该面墙时,输出墙体西侧或南侧方格区域的行和列以及该墙在方格区域中的位置方位(东用E表示,北用N表示)。

例如,我们要拆掉图例中箭头所指的墙,该墙南北是墙体,东西是方格区域,因此用它西侧的方格区域形容它的位置,它位于(4,1)方格的东侧,因此它的位置表示为 4 1 E。

当移除方法不唯一时,优先选择对应方格区域更靠西的墙,如果仍存在多解,选择对应方格区域更靠南的墙。

在此基础上,还存在多解(即可选的两个墙都对应同一方格区域),那么优先选择北面的墙。

数据范围
1≤M,N≤50

输入样例

7 4
11 6 11 6 3 10 6
7 9 6 13 5 15 5
1 10 12 7 13 7 5
13 11 10 8 10 12 13

输出样例

5
9
16
4 1 E

二、题解

其含义就是,给定了一个图,图中有很多的小房间,每个房间的西北东南(左上右下)可能有墙壁,也可能没有墙壁。每个房间的墙壁情况是用一个数字表示的,这个数字的二进制表示从第1位到第4位为1的话,分别表示小房间的左上右下有墙壁。
没有墙壁的地方,自然就可以让两个小房间连到一个,最后形成多个大房间。
题目首先要求我们输出大房间的个数,再输出最大房间的面积。
针对这两点我们用并查集实现,具体描述见代码注释。
还要求我们输出移除一面墙壁后可以获得的房间最大面积,以及该墙壁的位置。思路就是遍历每一面墙壁,对移除该墙壁后获得新房间的大小进行比较,然后记录最优情况。这里的关键在于遍历的顺序,这一点在代码注释中也有描述。

三、AC代码

/*
遍历房间和其墙壁,没有墙壁的情况下就合并两个房间(并查集)
并查集记录大房间数目再加一个size数组记录并查集中元素个数(即房间大小)

遍历所有墙壁,查看去掉这个房间后形成的房间大小
如果更大,那就更新ansx, ansy, ansDis,这三个变量是描述这个墙壁的位置

遍历墙壁的顺序是自左向右,自下向上
所以,最先遍历的墙壁应该是左下角房间的上墙壁,然后向上遍历这一列房间的上墙壁
接下来遍历左下角房间的右墙壁,然后向上遍历这一列房间的右墙壁

依次房间的列数自左向右,墙壁则是先遍历这一列的上墙壁,再遍历这一列的右墙壁
注意检查删除墙壁的合法性,不可以删除边界墙壁
*/
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 55;
char ansDis;
int roomNum, maxSize, ansx, ansy;
int n, m, g[N][N];
int uf[N * N], roomSize[N * N];
int dis[4][2] = {0, -1, -1, 0, 0, 1, 1, 0}; // left, up, right, down

void init();
int get(int x);
void merge(int x, int y);
bool check(int x, int y);
int getRoomNum(int &maxSize);
void removeWall(int di, int i, int j);

int main()
{
    cin >> m >> n;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
        {
            cin >> g[i][j];
        }

    init();
    for (int i = 0; i < n; i++)
    {
        // 合并初始状态下能合并的房间
        for (int j = 0; j < m; j++)
        {
            int num = g[i][j];
            // cout << num << endl;
            for (int d = 0; d < 4; d++)
            {
                if ((num & 1) == 0)
                { //这个方向没有墙
                    // cout << "kk\n";
                    int nxtx = i + dis[d][0];
                    int nxty = j + dis[d][1];
                    int nownum = i * m + j;
                    int nxtnum = nxtx * m + nxty;
                    if (check(nxtx, nxty) && get(nownum) != get(nxtnum))
                    { // (nxtx, nxty)vanlid
                        merge(nownum, nxtnum);
                    }
                }
                num >>= 1;
            }
        }
    }

    // roomNum, maxSize
    roomNum = getRoomNum(maxSize);
    cout << roomNum << endl
         << maxSize << endl;

    for (int j = 0; j < m; j++)
    {
        // remove wall
        for (int i = n - 1; i >= 0; i--)
        {
            int num = g[i][j];
            if (num & 1 << 1)
            {
                // 上方
                removeWall(1, i, j);
            }
        }

        for (int i = n - 1; i >= 0; i--)
        {
            int num = g[i][j];
            if (num & 1 << 2)
            {
                // right
                removeWall(2, i, j);
            }
        }
    }

    cout << maxSize << endl
         << ansx + 1 << ' ' << ansy + 1 << ' ' << ansDis << endl;

    system("pause");
    return 0;
}

void init()
{
    for (int i = 0; i < n * m; i++)
    {
        // init uf and roomSize
        uf[i] = i;
        roomSize[i] = 1;
    }
}

int get(int x)
{
    return uf[x] == x ? x : uf[x] = get(uf[x]);
}

void merge(int x, int y)
{
    // 先更新size,再合并并查集
    // 如果x和y已经在一个集合中了,就不要再进行合并了,因为会重复加size
    // 所以在使用merge的外层判断中有get(x) != get(y)
    roomSize[get(x)] += roomSize[get(y)];
    uf[get(y)] = get(x);
}

bool check(int x, int y)
{
    return x >= 0 && x < n && y >= 0 && y < m;
}

int getRoomNum(int &maxSize)
{
    int roomNum = 0;
    maxSize = 0;
    for (int i = 0; i < n * m; i++)
    {
        if (get(i) == i)
        {
            roomNum++;
            maxSize = max(maxSize, roomSize[i]);
        }
    }
    return roomNum;
}

void removeWall(int di, int i, int j)
{
    int nxtx = i + dis[di][0];
    int nxty = j + dis[di][1];
    int nownum = i * m + j;
    int nxtnum = nxtx * m + nxty;
    if (check(nxtx, nxty) && get(nownum) != get(nxtnum))
    {
        int newroomSize = roomSize[get(nownum)] + roomSize[get(nxtnum)];
        if (newroomSize > maxSize)
        {
            maxSize = newroomSize;
            ansx = i;
            ansy = j;
            ansDis = di == 1 ? 'N' : 'E';
        }
    }
}

如果我的题解对您有帮助,还望您不吝点赞。
欢迎讨论交流,期待共同进步!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值