[含泪总结] 1365: [蓝桥杯2018初赛]全球变暖(dfs+bfs)

本文介绍了如何利用深度优先搜索(DFS)和回溯算法解决全球变暖导致的岛屿完全淹没问题。在给定的海域照片中,预测因海平面上升而被完全淹没的岛屿数量。通过示例代码详细解释了DFS和BFS两种方法,以及在解题过程中的关键策略和注意事项。同时,文章提供了相关算法的背景知识,包括回溯法的基本思想和步骤,以及深度优先搜索与广度优先搜索的区别。

1365: [蓝桥杯2018初赛]全球变暖

时间限制: 1 Sec  
内存限制: 256 MB

题目描述
你有一张某海域NxN像素的照片,".“表示海洋、”#"表示陆地,如下所示:

.##…
.##…
…##.
…####.
…###.

其中"上下左右"四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有2座岛屿。
由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。
具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。
例如上图中的海域未来会变成如下样子:




…#…


请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。
输入
第一行包含一个整数N。 (1 <= N <= 1000)
以下N行N列代表一张海域照片。
照片保证第1行、第1列、第N行、第N列的像素都是海洋。
输出
一个整数表示答案。
样例输入 Copy
7

.##…
.##…
…##.
…####.
…###.

样例输出 Copy
1

如果对深度优先搜索与回溯有疑惑的可以看这里,没有疑惑的童鞋直接跳过此段

回溯(英语:backtracking)
回溯法是暴力搜索法中的一种。
对于某些计算问题而言,回溯法是一种可以找出所有(或一部分)解的一般性算法,尤其适用于约束满足问题(在解决约束满足问题时,我们逐步构造更多的候选解,
并且在确定某一部分候选解不可能补全成正确解之后放弃继续搜索这个部分候选解本身及其可以拓展出的子候选解,转而测试其他的部分候选解)。
1、基本思想
在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,
如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。(其实回溯法就是对隐式图的深度优先搜索算法)。
若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。
而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。
2、用回溯法解题的一般步骤:
(1)针对所给问题,确定问题的解空间:
首先应明确定义问题的解空间,问题的解空间应至少包含问题的一个(最优)解。
(2)确定结点的扩展搜索规则
(3)以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索。
‘’’
‘’’
其实回溯算法关键在于:不合适就退回上一步
然后通过约束条件, 减少时间复杂度.
‘’’
深度优先DFS
什么是DFS?
所谓的深度优先指的是从某个状态开始,不断地转移到下一个状态,直到不能继续转移为止,这时候返回前一步,转移到其他的状态,直到到达解状态。
emmm有亿点抽象,那…具体在这个迷宫问题上来看呢?
在迷宫问题上翻译一下深度优先的定义的话:就是说从某个格子出发,不断地往四周的格子走,如果考虑不走回头路的话,那么总会出现不能继续走的情况。这时候,退回到上一步,在上一步走其他的路,如果上一步没有其他的路可走了就再退回到上一步…不断重复这个过程,直到走到终点为止。
这就很直观了,就像一个人通过试错法来走迷宫的过程:先一路走到死路,然后回到前一步选另一条路,不断地试错,这样就遍历了所有能走的路。事实上也就是一种回溯法。
画成搜索树的话就是这样👇
在这里插入图片描述
这个搜索过程对应LIFO的栈结构:不断push新的状态入栈,不能继续时,pop出最后push的状态,push另一个状态。而众所周知,所有的栈操作和递归函数是等价的,所以DFS通常用递归来实现。
严格来讲,我这里由于采用了离开的时候才标记的策略,把状态修改给延后了。
如果按照到达时就标记的策略,应该把状态修改塞到每个if分支里。
也就是每个if分支都该是一个完整的
状态转移→递归调用→状态还原过程

回到正文
我们在思考的过程中会想到这样一种情况,一片陆地,被海域淹没成几块仅存的小陆地,幸存下来的陆地是初始时的高地,即上下左右四个方向都紧挨陆地
所以只要搜索初始的某个陆地连通块中是否存在高地,在查找连通块时,cnt_gd标记当前搜索的连通块中的高地数量,搜索完当前连通块后,检查cnt_gd数目是否为零,据此来判断是否存在高地
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
int n,gd,ans=0;
bool value[1010][1010];
bool check(string str[],int x,int y){
	if(str[x-1][y]=='#'&&str[x+1][y]=='#'&&str[x][y-1]=='#'&&str[x][y+1]=='#'){
		return true;
	}
}
void dfs(string str[],int x,int y){
	if(!value[x][y-1]&&str[x][y-1]=='#'){
		value[x][y-1]=true;
		if(check(str,x,y-1)) gd++;
		dfs(str,x,y-1);
	}
	if(!value[x][y+1]&&str[x][y+1]=='#'){
		value[x][y+1]=true;
		if(check(str,x,y+1)) gd++;
		dfs(str,x,y+1);
	}
	if(!value[x-1][y]&&str[x-1][y]=='#'){
		value[x-1][y]=true;
		if(check(str,x-1,y)) gd++;
		dfs(str,x-1,y);
	}
	if(!value[x+1][y]&&str[x+1][y]=='#'){
		value[x+1][y]=true;
		if(check(str,x+1,y)) gd++;
		dfs(str,x+1,y);
	}
	
}
int main(){
	
	cin>>n;
	string str[n];
	for(int i=0;i<n;i++){
		cin>>str[i];
	}
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			if(!value[i][j]&&str[i][j]=='#'){
				value[i][j]=true;
				gd=0;
				dfs(str,i,j);
				if(gd==0) ans++;
			}
		}
	}
	cout<<ans;
}

广度优先BFS
什么是BFS

BFS和DFS很类似,区别只在于搜索的顺序不同:BFS总是先搜索距离初始状态近的状态。
也就是说,它是按照 开始状态→1次转移可以到达的所有状态→2次转移可以到达的所有状态→…… 这样的顺序进行搜索。
在迷宫的例子里,就是说我们按照走1步能到达的位置,走2步能到达的位置…这样的顺序逐一搜索,直到不再有能到达的位置。
想象我们拿着地图,画出一系列的“等高线”,先标距离为1的,再标距离为2的,直到地图上的每一个位置都被标上了要到达所需的步数。
画成搜索树的话就是这样👇
在这里插入图片描述
这个搜索过程对应 FIFO 的队列结构:enqueue加入一层的状态,再dequeue丢出最先加入的那一层状态。当然由于BFS依赖于“步数”的概念,所以有时也可以考虑直接利用循环来实现
不懂pair的童鞋可以参考一下,懂直接跳过
Pair容器
类模板:template <class T1, class T2> struct pair
参数:T1是第一个值的数据类型,T2是第二个值的数据类型。
功能:pair将一对值组合成一个值,这一对值可以具有不同的数据类型(T1和T2),两个值可以分别用pair的两个公有函数first和second访问。
具体用法:
//1.定义(构造):
pair<int, double> p1; //使用默认构造函数
pair<int, double> p2(1, 2.4); //用给定值初始化
pair<int, double> p3(p2); //拷贝构造函数
//2.访问两个元素(通过first和second):
pair<int, double> p1; //使用默认构造函数
p1.first = 1;
p1.second = 2.5;
cout << p1.first << ’ ’ << p1.second << endl;
//3.赋值operator = :
//(1)利用make_pair:
pair<int, double> p1;
p1 = make_pair(1, 1.2);
//(2)变量间赋值:
pair<int, double> p1(1, 1.2);
pair<int, double> p2 = p1;
回到正文
bfs与上面思路一样,只不过上面用的是dfs

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

string mp[1001];
int cnt_gd;//标记每个连通块中高地的数目
int cnt=0;//被完全淹没的岛屿
int go[4][2]={{-1,0},{0,1},{1,0},{0,-1}};
struct node{
	
};
void bfs(int x,int y){
	queue<pair<int,int> > q;//最后面两个尖括号需要用空格隔开
	q.push({x,y});
	mp[x][y]='2';
	while(!q.empty()){
		for(int i=0;i<4;i++){
			int now_x=q.front().first+go[i][0];
			int now_y=q.front().second+go[i][1];
			if(mp[now_x][now_y]=='1'||mp[now_x][now_y]=='#'){
				if(mp[now_x][now_y]=='1') cnt_gd++;
				mp[now_x][now_y]='2';//将陆地标记为2,对整个岛都进行标记,防止重复计算
				q.push({now_x,now_y});
			}
		}
		q.pop();
	}
}
int main()
{
	int n;
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>mp[i];
	}

	//修改地图
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			if(i-1>=0&&j-1>=0&&i+1<=n-1&&j+1<=n-1&&mp[i-1][j]!='.'&&mp[i+1][j]!='.'&&mp[i][j-1]!='.'&&mp[i][j+1]!='.'){
				mp[i][j]='1';//标记高地为1
			}
		}
	}

	//查找连通块
	for(int i=0;i<n;i++){
		for(int j=0;j<n;j++){
			if(mp[i][j]=='#'){
				cnt_gd=0;
				bfs(i,j);
				if(cnt_gd==0) cnt++;
			}
		}
	}

	cout<<cnt;
	return 0;
}

看上去还挺简单的,有收获的可以关注+点赞
在这里插入图片描述

大部分内容借鉴于https://github.com/Locietta/blogs/issues/14.
以及https://github.com/BBuf/ACM_template/blob/master/%E5%B8%B8%E7%94%A8%E6%8A%80%E5%B7%A7/%E5%B8%B8%E7%94%A8STL%E5%8F%8A%E5%85%B6%E7%94%A8%E6%B3%95.md.

<think>嗯,用户需要2018蓝桥杯C++试题中关于全球变暖的题解。首先,我得回忆一下这个题目的具体内容。题目应该是在一个N×N的网格中,每个格子代表一块陆地或海洋。题目要求确定有多少岛屿会被完全淹没,也就是原本的岛屿中,有多少岛屿的所有陆地格子都与海洋相邻,导致全球变暖后这些陆地全部变成海洋。 接下来,我需要考虑如何解决这个问题。首先,要理解岛屿的定义。岛屿是由相邻的陆地组成的,上下左右四个方向相邻的陆地属于同一个岛屿。然后,全球变暖导致所有临海的陆地(即上下左右有至少一个海洋的陆地)都会被淹没。因此,如果一个岛屿中的所有陆地格子都至少有一个相邻的海洋,那么这个岛屿会被完全淹没;反之,如果有至少一个陆地格子周围没有海洋,那么这个岛屿就不会被完全淹没,因为那个陆地不会被淹没,岛屿仍有残留。 那解决步骤应该是这样的:首先遍历整个网格,找到所有岛屿,然后对每个岛屿检查是否所有陆地格子都是临海的。如果是,则统计到结果中。否则不统计。 具体实现的话,可以用深度优先搜索(DFS)或广度优先搜索(BFS)来标记和遍历每个岛屿。对于每个岛屿中的每个陆地格子,检查其四周是否有海洋。如果存在至少一个陆地格子四周都没有海洋,则整个岛屿不会被完全淹没;否则,该岛屿会被统计。 需要注意的问题包括网格的边界处理,确保数组不越界。此外,如何高效地遍历每个岛屿,并标记已访问的格子,避免重复计算。 然后,我需要参考用户提供的引用链接中的代码。根据用户提供的引用链接,可能这两个博客中有相关的题解。但用户现在无法直接访问这些链接,所以我需要根据常见的解法来构建答案。例如,使用BFS遍历每个岛屿,并为每个岛屿记录是否存在不会被淹没的陆地。 可能的代码结构是:读取输入,遍历每个格子,如果是未访问的陆地,则进行BFS,同时检查该岛屿中是否存在至少一个陆地格子四周没有海洋。如果不存在这样的格子,说明整个岛屿会被淹没,计数器加一。 然后,生成相关问题,可能涉及算法优化、不同解法比较、题目变种等。 现在,把这些思路整理成结构化的答案,确保符合用户要求的格式,比如使用中文,行内数学表达式用$...$,独立公式用$$...$$,正确使用LaTeX语法,并生成相关问题。</think>### 蓝桥杯2018 C++ 全球变暖题解 #### 问题描述 给定$N×N$的网格地图,每个单元格包含`#`(陆地)或`.`(海洋)。全球变暖导致**四周存在海洋的陆地**被淹没。要求计算有多少岛屿会完全消失(即原岛屿中所有陆地均被淹没)[^1]。 #### 解题思路 1. **岛屿识别**:使用BFS/DFS遍历连通陆地,标记已访问区域 2. **淹没判断**:对每个岛屿检查是否存在至少一个陆地单元格满足: $$ \forall \text{相邻方向} \ (dx,dy),\ grid[x+dx][y+dy] ≠ "." $$ 即该陆地四周无海洋,属于不会被淹没的"安全点" #### 代码实现 ```cpp #include <iostream> #include <queue> using namespace std; const int N = 1005; char grid[N][N]; bool vis[N][N]; int dir[4][2] = {{-1,0}, {1,0}, {0,-1}, {0,1}}; bool hasSafePoint(int x, int y, int n) { for(int i=0; i<4; i++){ int nx = x + dir[i][0]; int ny = y + dir[i][1]; if(nx<0 || nx>=n || ny<0 || ny>=n) continue; if(grid[nx][ny] == '.') return false; } return true; } int main() { int n, res = 0; cin >> n; for(int i=0; i<n; i++) cin >> grid[i]; for(int i=0; i<n; i++) { for(int j=0; j<n; j++) { if(grid[i][j]=='#' && !vis[i][j]) { queue<pair<int,int>> q; q.push({i,j}); vis[i][j] = true; bool hasSafe = false; while(!q.empty()) { auto [x,y] = q.front(); q.pop(); if(hasSafePoint(x,y,n)) hasSafe = true; for(int d=0; d<4; d++) { int nx = x + dir[d][0]; int ny = y + dir[d][1]; if(nx>=0 && nx<n && ny>=0 && ny<n && grid[nx][ny]=='#' && !vis[nx][ny]) { vis[nx][ny] = true; q.push({nx,ny}); } } } if(!hasSafe) res++; } } } cout << res << endl; return 0; } ``` #### 算法分析 - 时间复杂度:$O(N^2)$,每个单元格最多访问一次 - 空间复杂度:$O(N^2)$,存储访问标记和队列空间 - 核心技巧:在BFS过程中同时进行安全点检测,避免二次遍历
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值