初识宽度优先搜索(BFS)(二)

P1141 01迷宫

题目描述
有一个仅由数字00与11组成的n \times nn×n格迷宫。若你位于一格00上,那么你可以移动到相邻44格中的某一格11上,同样若你位于一格11上,那么你可以移动到相邻44格中的某一格00上。

你的任务是:对于给定的迷宫,询问从某一格开始能移动到多少个格子(包含自身)。

输入格式
第11行为两个正整数n,mn,m。

下面nn行,每行nn个字符,字符只可能是00或者11,字符之间没有空格

接下来mm行,每行22个用空格分隔的正整数i,ji,j,对应了迷宫中第ii行第jj列的一个格子,询问从这一格开始能移动到多少格。

输出格式
mm行,对于每个询问输出相应答案。

输入
2 2
01
10
1 1
2 2
输出
4
4

我的思路

拿道题,输入为迷宫类型的,故使用迷宫的做法,但是每次询问都要重新搜,故超时了。
注:这里maze一定要设置为char类型,若为int类型,会读不进去,因为俩数据之间没有空格,所以10会被看成一个数据,多磨痛的领悟(我不会告诉你我这个点卡了一个小时)

#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;

typedef pair<int ,int > P;
const int MAX_N = 1005;
const int MAX_M = 100005;

int n,m;
char maze[MAX_N][MAX_N];
int g,h;
P p; 
int d[MAX_N][MAX_N];
int dx[4] = {0,0,1,-1},dy[4] = {1,-1,0,0};
bool check(int x,int y)
{
	return 1 <= x && x <= n && 1 <= y && y <= n;
}

int bfs()
{
	int ans = 1;
	memset(d,-1,sizeof(d));
	queue<P> que;
	que.push(P(g,h));
	d[g][h] = 0;
	while (!que.empty())
	{
		p = que.front();
		que.pop();
		for (int i = 0;i < 4;i ++)
		{
			int x = p.first + dx[i],y = p.second + dy[i];
			if (check(x,y) && maze[x][y] != maze[p.first][p.second] && d[x][y] == -1)
			{
				que.push(P(x,y));
				d[x][y] = 0;
				ans ++;
			}
		}
	}
	return ans;
}

int main()
{
	scanf("%d%d",&n,&m);
	for (int i = 1;i <= n;i ++)
		scanf("%s",maze[i] + 1);
	for (int i = 0;i < m;i ++)
	{
		scanf("%d%d",&g,&h);
		printf("%d\n",bfs());
	}
	return 0;
}

当看到询问的次数那么多,如果每一次询问再去BFS,那么结果可想而知。

如果有非常多的询问,眼看着一定会超时,那么就需要考虑把答案存起来,所以这个题就可以把答案存起来,然后当它询问时,就可以直接输出答案了。
再一个就是此题为连通图题(洪水填充题),故将每个连通图标识出来。

#include <bits/stdc++.h>
using namespace std;

const int MAX_N = 1005;
const int MAX_M = 10000005;

int n,m,x,y;
struct maze
{
	char c;
	bool is;
}a[MAX_N][MAX_N];
int quex[MAX_M],quey[MAX_M];//通过数组实现的队列将遍历过的点存储起来。
int l = 1,r = 0;//l为队头,r为队尾。
int ans[MAX_N][MAX_N];
int dx[5] = {0,1,-1,0,0},dy[5] = {0,0,0,-1,1};//下标为0时,啥也不是,只是为了方便表达。

int main()
{
	scanf("%d%d",&n,&m);
	for (int i = 1;i <= n;i ++)
		for (int j = 1;j <= n;j ++)
			scanf(" %c",&a[i][j].c);
	for (int i = 1;i <= n;i ++)
	{
		for (int j = 1;j <= n;j ++)
		{
			if (a[i][j].is == 1)
				continue;
			l = 1,r = 0;
			quex[++r] = i;
			quey[r] = j;
			a[i][j].is = 1;
			int cnt = 1;//自身也算
			while (l <= r)
			{
				for (int k = 1;k <= 4;k ++)
				{
					int nx = quex[l] + dx[k];
					int ny = quey[l] + dy[k];
					if (nx < 1 || nx > n || ny < 1 || ny > n) continue;
					if (a[nx][ny].c == a[quex[l]][quey[l]].c) continue;
					if (a[nx][ny].is == 1) continue;
					a[nx][ny].is = 1;
					quex[++r] = nx;
					quey[r] = ny;
					cnt ++;
				}
				l ++;
			}
			//ans[i][j] = cnt;//k=1时,存的就是i和j可能大佬不放心所以加上的。
			for (int k = 1;k <= r;k ++)
			{
				ans[quex[k]][quey[k]] = cnt;//构成连通图。
			}
		}
	}
	while (m --)
	{
		scanf("%d%d",&x,&y);
		printf("%d\n",ans[x][y]);
	}
	return 0;
}

看了orz的题解深有感触。

总结1:

迷宫新型实现方式:
struct maze
{
	char c;
	bool is;//相当于book数组,来标记是否被访问。
}a[MAX_N][MAX_N];
int quex[MAX_M],quey[MAX_M];//这里开的数组大小根据要存储的点数的最大值来决定。
int l = 1,r = 0;

这种方法可以存储所遍历的点,可以解决连通图等问题。
其中,当l > r时,队列为空,当l <= r时,队列不为空,入队操作:r ++,出队操作:l ++。

c语言实现的队列太鸡卵了,只能存数值。

struct queue
{
	int data[];//改成qux[],quey[]又太麻烦了。 
	int head;//队首 
	int tail;//队尾 
}

又看了orz的一篇题解,用的是暴力深搜。

大佬的思路:

迷宫中被划分出来很多个连通图,如果给每个连通图上色的话,为相同颜色,开动脑筋想一下,可以用不同数字来代替不同颜色。
在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;

const int MAX_N = 1010;
const int MAX_M = 1000005;

char maze[MAX_N][MAX_N];
int iss[MAX_N][MAX_N];//不仅标记了遍历的点,还标记了连通图。
int ans[MAX_M];
int n,m,x,y,d = 1;
int total;
int dx[4] = {0,0,1,-1},dy[4] = {1,-1,0,0};

bool check(int x,int y)
{
	return x <= n && x >= 1 && y <= n && y >= 1;
}
inline void dfs(int x,int y)
{
	iss[x][y] = d;//这里的也可以写到遍历的循环体里面。
	total ++;
	for (register int i = 0;i < 4;i ++)
	{
		int nx = x + dx[i],ny = y + dy[i];
		if (check(nx,ny) && iss[nx][ny] == 0 && maze[x][y] != maze[nx][ny])
		{
			dfs(nx,ny);
			//iss[nx][ny] = d;
			//total ++;
		}
	}
}

int main()
{
	//std::ios::sync_with_stdio(false);
	scanf("%d%d",&n,&m);
	for (register int i = 1;i <= n;i ++)
		for (register int j = 1;j <= n;j ++)
		{
			std::cin >> maze[i][j];
		}
	for (register int i = 1;i <= m;i ++)
	{
		std::cin >> x >> y;
		if (iss[x][y] == 0)
		{
			total = 0;
			//total = 1;
			//iss[x][y] = d;
			dfs(x,y);
			ans[d++] = total;//记录每个连通图的点数,用d来记录第几个连通图。
		}
		std::cout << ans[iss[x][y]] << "\n";
	}
	return 0;
}

这位大佬的知识犹如深海,但人不是生来要给打败的,一个人能够被毁灭掉,但不能被打败。

总结2:

1.自加自减

对于后加加,加加操作都在表达式之后进行。
对于前加加,加加操作都在表达式之前进行,但是会考虑表达式内部的结合顺序,即考虑子表达式的问题,进行逐步加加的。

2.C语言register关键字—最快的关键字

register:这个关键字请求编译器尽可能的将变量存在CPU内部寄存器中,而不是通过内存寻址访问,以提高效率。注意是尽可能,不是绝对。

因为,如果定义了很多register变量,可能会超过CPU的寄存器个数,超过容量。所以只是可能。

3.std::ios::sync_with_stdio(false);
4.C++中std的使用和作用
5.inline内联函数的标记

最近更新时间:2021/05/15 -> 2021/05/22

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值