【圣明先生】宽搜 地雷排查

吉林大学毕业生 金圣明 内部题库 2023.05.07

感谢周周老师提供的题目

题目 地雷排查

一个N*N的方格上,遍布着一些地雷,有雷的位置我们用1来表示,没有雷的位置用0来表示。有雷的位置会与他相邻位置上、下、左、右的雷组成雷区。现在给你这样的一个矩阵,请问一共有多少个雷区。

输入样例:

输入格式

第一行为一个整数 N,接下来是一个N*N的矩阵

输出格式

输出一共有多少个雷区

数据范围

1≤N≤1000

输入样例:

5
1 1 1 0 0
1 0 1 0 0
0 0 0 1 1
1 0 0 1 0
0 0 0 1 0

输出样例:

3


分析过程

首先,最重要的一点我先剧透了

题目本身没有坑

芜湖!起飞!(我在激动些什么


这道题是一道BFS宽搜的模板级题目。模板级是个什么级别呢?就像你拧螺丝钉的时候,会看一下是十字还是一字的一样。当你看到螺丝钉,就应该去用对应的螺丝刀的程度,就是模板题

那么,简单宽搜题目的特征是什么,我们来学习一下

  • 二维的符号或数字表示的
  • 寻找最短路径最短路径数量染色
  • 图有明确边界不一定是,图没有明确边界一定是
  • 经验判断:图很大,大概有10万或以上的坐标。

很少,很简单。

我们看这道题的输入方式,就能理解条件1是什么意思了。我再举几个例子

在这里插入图片描述
在这里插入图片描述
上面两道题,只看输入输出是这样的分布,99%宽搜,没跑的。

当然,如果是提供坐标画图的那种,本质上也是个固定大小的二维图,不要钻牛角尖啊也是宽搜。

条件2也很好理解。宽度优先搜索找单对多最短路有一手的。

条件3是个条件。对于无界或者无限图,不能使用深搜这个不太需要解释。但是宽搜对于向外射线方向不收敛的情况(如球体),经过一定优化是能hold注的。
↑这里不细说,遇到了再掰开了说。

图的大小和条件三也是有关的。状态太多的情况下,深搜必炸。因为深搜的本质是什么?

是 暴 力

是 暴 力

是 暴 力

一定要铭记在心。宽搜是一种类DP行为,会忽略路径只记录距离,这个特质非常好。

那么为什么会想到用BFS,我用两种方式解释了。第一,这是模板题,看到就应该知道用宽搜;第二,这道题是从一个点去找相邻的点,属于二维图染色题,归宽搜管。

宽搜是什么,有哪些模板题,这些内容后面我会单独写一个专题(请关注我!),敬请期待。


回到这道题本身上,需要关注的地方就是,染色这个行为应该怎么去完成,我提供一个简单的思路。

for (i  1到N)
{
	for (j  1到N)
		如果该位置本身含雷,并且未被标记染色(或者说被标记为雷区的一部分)
		{
			将该位置置入队列并标记为占用
			while 队列不为空
			{
				移除队列头部元素X
				将X四周的有雷位置放入队列并标记占用
			}
			计数器+1
		}	
}

我:就这些,没了,翻译一下伪代码就过了。
学生:……这么简单?
我:啊?不然呢?

宽搜真不难。深搜的本质是堆栈,宽搜的本质是队列。当我们走出受限的视角、以更广阔的视野去观察题目,那么就会发现,数据结构和算法确实是有一种关系在其中的。两个东西不能分开去学。

抱着一种负责任的心态,我们看一下时间消耗。每一个位置最多入队列一次,这是100万次,那么询问是否能入队的次数:for循环开头的一次询问,X进入队列后会触发4次询问,一共500万。简直不要太友好。

空间消耗,,emmm本地编译器有一些不支持开100万longlong数组,我的建议是提交到OJ的时候再改大。这种事情时有发生,人不能被代码憋死是不是

虽然宽搜本身很简单,但是这道题还是涉及到了其他的一些知识点。比如四个方向的方向数组怎么写,队列应该怎么写,结构体应该怎么写。如果看不懂的话,这些东西以后我也会在合适的题目里慢慢更新的!(请关注我!)

以上! 代码就在下面,有问题可以加我的微信 MythLucky详聊哦!
// 吉林大学毕业生 金圣明 2023.05.07

#include<iostream>
using namespace std;
struct poi{
	int x;
	int y;
};
poi q[1000010],t;
int main()
{
	int dirx[10]={0,0,0,1,-1};
	int diry[10]={0,1,-1,0,0};
	int i,j,k,n,x,y,h=0,x1,y1,head,tail;
	int a[1010][1010];
	bool b[1010][1010];
	cin>>n;
	for(i=0;i<=n+1;i++)
	{
		a[0][i]=0;
		a[n+1][i]=0;
		a[i][0]=0;
		a[i][n+1]=0;
	}
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=n;j++)
		{
			cin>>a[i][j];
			b[i][j]=true;
			if(a[i][j]==0) b[i][j]=false;
		}
	}
	for(i=1;i<=n;i++)
	{
		for(j=1;j<=n;j++)
		{
			if(a[i][j]==1 && b[i][j])
			{
				b[i][j]=false;
				head=0;
				tail=-1;
				t.x=i;
				t.y=j;
				q[++tail]=t;
				while(tail>=head)
				{
					x=q[head].x;
					y=q[head].y;
					for(k=1;k<=4;k++)
					{
						x1=x+dirx[k];
						y1=y+diry[k];
						if(a[x1][y1]==1 && b[x1][y1])
						{
							b[x1][y1]=false;
							t.x=x1;
							t.y=y1;
							q[++tail]=t;
						}
					}
					head++;
				}
				h++;
			}
			b[i][j]=true;
			if(a[i][j]==0) b[i][j]=false;
		}
	}
	cout<<h;
	return 0;
}

插一句话,这个代码不是很完美,变量名是一个方面,还有状态数组b其实在这道题中是可以省略的,直接在a数组上面改01就ok,但宽搜模板本身就需要这个数组,那我就略显麻烦放在这里了

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值