吉林大学毕业生 金圣明 内部题库 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,但宽搜模板本身就需要这个数组,那我就略显麻烦放在这里了