程序设计II第三次实验题思路(炸毁一教bushi)

三更(补充了BFS思路)

2021-04-28  19:58:50


 

二更(补充了DFS思路)

2021-04-27  13:12:48


 

2021-04-22

11:13:40

题目如下:

一教的教室
贡献者程序设计2
提交情况
1 / 7

题目描述

由于一教需要安排各种各样的物理实验,每个实验需要的实验设备不一样大,因此需要重新规划一教的教室。

给定一教现在的平面图,首先你需要知道现在的一教有几个教室,每个教室有多大。

接下来你可以规划新的一教了。你只能拆掉其中的一堵墙,形成一个更大的教室。

你想知道,这样能形成的最大的教室的大小,以及需要拆掉的墙是哪一堵。

请仔细研究下面这个一教平面图:

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

其中#是墙体的位置, | 和 - 都没有墙,可以自由通行, -> 指向最优方案需要拆除的墙。

在上面的例子中,一教的大小是 7 * 4个小方块,共有5个教室。

一个教室指的是互相连通的小方块。这几个教室的大小分别是 1 , 3 , 7 , 8 , 9个小方块。

拆掉如图所示的墙,能形成的最大的教室的大小是 16

输入描述

第一行两个整数 m n ,表示一教有 n 行 m 列个小方块,请注意这里的行和列,接下来有 n 行输入,每行 m 个数字。

每一个小方块对应的数字告诉我们这个小方块的东西南北是否有墙存在。

每个数字是由以下四个整数中的任意个加起来的。

1: 在西面有墙

2: 在北面有墙

4: 在东面有墙

8: 在南面有墙

一教内部的墙会被规定两次。比如说 (1,1) 南面的墙,也会被标记为 (2,1) 北面的墙

对于 100% 的数据,1 ≤ n , m ≤ 50

输出描述

输出包含如下四行:

第一行:当前一教的教室数目。

第二行:当前最大教室的大小

第三行:移除一面墙能得到的最大的教室的大小

第四行:移除哪面墙可以得到面积最大的新教室。

有多解时选最靠西的,仍然有多解时选最靠南的。同一小方块北边的墙比东边的墙更优先。

用该墙的南面单位的北墙或西面单位的东墙来表示这面墙,方法是输出邻近单位的行数、列数和墙的方位( N 或者 E )。

样例输入

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
//需要注意最后有一个空行
本题使用思路:并查集,个人感觉dfs+记忆化也可以,后续题解会补充.(BFS也可以)
并查集后续也会出一篇专门博客来介绍模板题。

解题思路1(并查集):

并查集基本操作函数如下:
int find_pre(int x)     				//查找结点x的根结点 
{
    if (pre[x] == x) return x;			//递归出口:x的上级为x本身,即x为根结点 
    return pre[x] = find_pre(pre[x]);   //此代码相当于先找到根结点rootx,然后pre[x]=rootx 
}
bool Is_same(int x, int y)      		//判断两个结点是否连通 
{
    return find_pre(x) == find_pre(y);  //判断两个结点的根结点(亦称代表元)是否相同 
}
bool unite(int x, int y)
{
    x = find_pre(x);
    y = find_pre(y);
    if (x == y)
        return 0;
    if (_rank[x] > _rank[y]) pre[y] = x;		//令y的根结点的上级为x
    else
    {
        if (_rank[x] == _rank[y])  _rank[y]++;
        pre[x] = y;
    }
    return 1;
}

  


在此简单介绍下:

百度并查集
并查集是一种集合的数据结构,即来判断某些元素是否有关联同属于同一个结点,是否联通。具体模板题可参考洛谷P3367
我采用了将题目中的二维空间压缩为一维空间进行判断,判断是否联通的顺序是从左到右,从上到下,因此只需考虑该点的南方和东方即可,其余方向不用考虑。
(可以用二进制进行优化,见思路二)
 for (int i = 1; i <= n * m; i++)
    {
        switch (classroom[i])
        {
            case 0:
            case 1:
            case 2:
            case 3:
            {               
                if (i + m <= m * n)//南
                {
                    unite(i, i + m);
                }
                if (i % m != 0)//东
                {
                    unite(i, i + 1);
                }
                break;
            }
            case 4:
            case 5:
            case 6:
            case 7:
            {               
                if (i + m <= m * n)//南
                {
                    unite(i, i + m);
                }               
                break;
            }           
            case 8:
            case 9:
            case 10:
            case 11:
            {               
                if (i % m != 0)//东
                {
                    unite(i, i + 1);
                }
                break;
            }             
            default:
                break;
        }
    }   //建立关系

  


这样子,各个点的联通关系就建立好啦。接下来我们需要找出房间的数目(根节点的数目)以及最大的房间(根节点所对应的元素最多)。
 for (int i = 1; i <= m * n; i++)
    {
        root[find_pre(i)]++;//find_pre函数是寻找i的根节点,root就是根节点所对应格子的数目
    }

  在这里我们建立一个ismax函数来确定最大的房间

int ismax(int x)//参数x为格子的总数目即m*n(其实也可以无参)
{
    for (int i = 1; i <= x; i++)
    {
        if (root[i] != 0)
        {
            if (root[i] > Max)  Max = root[i];
            num++;//房间的总数目
        }//每出现一个新的根节点房间数目就加1
    }
    return Max;
}

  接下来我们只需要调用这个函数就可以得到最大房间即房间的总数目啦。

目前题目所要求输出的前两个要求我们已经完全完成,接下来就是刺激的炸毁一教环节。

我们重新定义一个参数MAX来代表炸毁某一面墙后的最大房间,MAX本身所取的最小值是ismax

那么我们只需当打破后的房间大小大于MAX时让它替代MAX即可

由于题目要求

有多解时选最靠西的,仍然有多解时选最靠南的。同一小方块北边的墙比东边的墙更优先。

用该墙的南面单位的北墙或西面单位的东墙来表示这面墙,方法是输出邻近单位的行数、列数和墙的方位( N 或者 E )。

故我们这里从西南角开始遍历,从下到上,从左到右,这里压成一维有些麻烦注意小心。同理我们只需判断某一格子的北面和东面。

for (k = 1; k <= m; k++) 
    {
        for (int i = (n - 1) * m + k; i > 0; i = i - m)
        {
            if (i - m > 0)//北
            {
                if (!Is_same(i ,i - m)) {

                    if (root[find_pre(i)] + root[find_pre(i - m)] > MAX)
                    {
                        MAX = root[find_pre(i)] + root[find_pre(i - m)];
                        M = i;//M是该点的一维位置坐标
                        des = 'N';//des为方向
                    }
                }
            }
            if (i % m != 0)//东
            {
                if (!Is_same(i, i + 1)) {
                    if (root[find_pre(i)] + root[find_pre(i + 1)] > MAX)
                    {
                        MAX = root[find_pre(i)] + root[find_pre(i + 1)];
                        M = i;
                        des = 'E';
                    }
                }
            }
        }
    }

  然后记得将一维转为二维输出即可

if (M % m == 0)
    {
        cout << MAX << endl << M / m << " " << m << " " << des;
    }
    else
        cout << MAX << endl << M / m + 1 << " " << M % m << " " << des;

解题思路2(DFS+记忆化):

(PS:此思路在作者提交时会被系统自动开的O2卡掉,因此可以开下面的指令来搞掉O2)

#pragma GCC optimize(0)

 

  

 

通过对每一个房间进行深搜找到最长的一条路即最大房间,同时可运用记忆化来提高代码运行速度。

建立一个三维数组来存放房间的位置和四方墙的有无,这里采用二进制进行判断读入

 

for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cin >> s;
            if (s & 1) room[i][j][2] = 1;
            if (s & 2) room[i][j][3] = 1;
            if (s & 4) room[i][j][0] = 1;
            if (s & 8) room[i][j][1] = 1;
        }
    }

 

然后对每一个格子进行遍历深搜,找到其最大房间,并记录该房间的大小。

for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++)
        {
            ans = 0;
            if (flag[i][j] == 0) {
                dfs(i, j);
                s++;//房间的数目
                roomsize[s] = ans;//房间的大小
            }
            maxn = max(maxn, ans);
        }
    }

深搜函数如下:

flag函数代表某一位置所属房间的编号,s为房间的数目,当该房间未被搜索过时,flag为0,搜索过后,flag为该房间的编号

int dfs(int x, int y)
{
    if (flag[x][y]!= 0|| x <= 0 || y <= 0 || x > n || y > m)
    {
        return 0;
    }
    ans++;
    flag[x][y] = s + 1;
    if (room[x][y][0] == 0 && !(flag[x][y+1] != 0 || x <= 0 || y <= 0 || x > n || y > m)) {//east
        dfs(x, y + 1);
    }
    if (room[x][y][1] == 0 && !(flag[x+1][y] != 0 || x <= 0 || y <= 0 || x > n || y > m)) {//south
        dfs(x + 1, y);
    }
    if (room[x][y][2] == 0 && !(flag[x][y - 1] != 0 || x <= 0 || y <= 0 || x > n || y > m)) {//west
        dfs(x, y - 1);
    }
    if (room[x][y][3] == 0 && !(flag[x-1][y] != 0 || x <= 0 || y <= 0 || x > n || y > m)) {//nouth
        dfs(x - 1, y);
    }
}

  然后炸墙环节和思路一基本相同,不再赘述

 for (int k = 1; k <= m; k++)
    {
        for (int i = n; i > 0; i = i - 1)
        {
            if (flag[i][k] != flag[i - 1][k])//nouth
            {
                if (roomsize[flag[i][k]] + roomsize[flag[i - 1][k]] > Max)
                {
                    xx = i, yy = k;
                    Max = roomsize[flag[i][k]] + roomsize[flag[i - 1][k]];
                    position = 'N';
                }
            }
            if (flag[i][k] != flag[i][k + 1])
            {
                if (roomsize[flag[i][k]] + roomsize[flag[i][k + 1]] > Max)
                {
                    xx = i, yy = k;
                    Max = roomsize[flag[i][k]] + roomsize[flag[i][k + 1]];
                    position = 'E';
                }
            }
        }
    }

 

解题思路3(BFS)

通过广度(宽度)优先搜索来找到最大房间。

关于BFS:BFS  BFS与DFS (www找到的讲的比较好的,侵删)

下面就来用BFS介绍一下本题的解题思路吧。(由于DFS和BFS有一定相似之处,所以部分代码可参考DFS部分)

关于输入部分,我们仍采用二进制读入,参考DFS部分。

然后就到了最最重要的部分——bfs函数

解释大都放到了注释部分,不详细讨论了

 

void bfs(void)
{
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			if (!flag[i][j])//flag函数为标记函数,值为所在坐标的房间号,初始值为0
			{
				flag[i][j] = ++num;
				xx[1] = i;//存一下,后面需要使用
				yy[1] = j;
				int front = 0, near = 1, sum = 1;;
				while (front < near)//宽搜开始
				{
					front++;
					int _x, _y;//变换后的坐标
					for (int k = 0; k <= 3; k++)//if writes {int k=0;while(k++=3){}},false,这是第一遍作者写的一个bug点www
					{
						_x = xx[front] + Des[k][0];//Des为二维的位置坐标,用来表示四个方向
						_y = yy[front] + Des[k][1];
						if (_x > 0 && _y > 0 && _x <= n && _y <= m && !flag[_x][_y] && !classroom[xx[front]][yy[front]][k])
						{
							flag[_x][_y] = num;
							sum++;
							xx[++near] = _x;
							yy[near] = _y;
						}
					}
				}
				roomsize[num] = sum;
				Max = max(sum, Max);
			}
		}
	}
}
// int Des[4][2] = { {0,-1}, {-1,0} ,{0,1} ,{1,0} }, xx[2601], yy[2601];

 

  后面的炸墙和DFS完全相同,不再重复。

 

 

 

 

完结啦,撒花~

欢迎大家与我讨论更好的思路wwwww

posted @ 2021-04-22 11:04  黯战丿风魂  阅读(185)  评论(0)    收藏  举报