算法如山行则将至
题目描述
找出所有无法到达边界的0并将其改为2。
思考与重做
廖夏的同题记录 · 2 条
- 廖夏 · 2026-10-10本次记录最后更新 2026.10.10
完成结果:提示后完成仍在学习
进一步理解了dfs,可以严格证明vis的永久写法和取消写法一样对于连通性没有损失,本题必须采用永久写法
可达性具有传递性,可以用如下逻辑证明:
由起点S到重点G存在若干个点,若 $S\leadsto A$,且 $A\leadsto B$,则必有 $S\leadsto B$,以此类推,若存在一条连接若干点的通路可达G,则路上的点均可达G。可达具有传递性,因此即使使用了永久标记,但是可达性可以一直传递,永久标记只会保证不走回头路,此前的搜索已经覆盖了所有可达的区域,但是不会破坏可达性。用$R_k$表示可达点的集合,则搜索算法做的就是不断求解$R_{k+1} = R_k \cup New(R_k)$,直到 $R_k = R_{k+1}$。这一过程对应 BFS 的逐层扩展:BFS 按照节点到起点的最短路径距离,由近及远地访问节点。DFS 则优先沿一条路径深入,直到无法继续,再回溯探索其他分支。它不按照距离逐层扩展,但最终得到的可达节点集合与 BFS 相同。
连通性搜索只关心节点是否可达,不关心具体通过哪条路径到达。因此,一个节点通常只需要访问一次。而路径枚举问题关心不同的访问路径,同一个节点在不同路径下可能对应不同的搜索状态,因此往往需要回溯取消 vis。两者的本质区别是:搜索状态是否仅由当前节点决定,还是还依赖于到达该节点的路径历史。
此前的尝试
完成结果:提示后完成仍在学习已结束复习安排
卡点:忽略了一些DFS的经典错误: 1. vis 表示“在当前搜索过程中已经访问”,它是否能跨多次 DFS 保留,取决于任务;如果每个起点都是一次独立查询,就必须重新清空。 2. DFS 中间节点的返回值不一定等于“以该节点重新作为起点”的答案,因为无法走回头路,而回头路有可能是唯一通路
展开当时的复盘
存在两种思路:正常的灌水法和诡异的每个点都搜一遍 启示:DFS 的本质是沿“可达关系”遍历状态;二维网格只是图的一种具体表现。
代码
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
const int MAXN = 35;
bool a[MAXN][MAXN];
bool vis[MAXN][MAXN];
int n;
int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};
bool dfs(int x, int y){
vis[x][y] = true;
if (a[x][y]) return 1;
if (x == 1 || y == 1 || x == n || y == n) return 0;
bool res = 1;
for (int i = 0; i < 4; i++){
int nx = x + dx[i], ny = y + dy[i];
if (nx >= 1 && ny >= 1 && nx <= n && ny <= n && !vis[nx][ny]){
// vis[nx][ny] = true;
res = min(res, dfs(nx,ny));
// vis[nx][ny] = false;
}
}
return res;
}
int main(){
cin >> n;
for (int i = 1; i <= n; i++){
for (int j = 1; j <= n; j++){
cin >> a[i][j];
}
}
for (int i = 1; i <= n; i++){
for (int j = 1; j <= n; j++){
if (i == 1 || j == 1 || i == n || j == n) {cout << a[i][j] << " "; continue;}
memset(vis, 0, sizeof(vis));
if (a[i][j]) cout << 1 << " ";
else {
vis[i][j] = true;
cout << (dfs(i, j) ? 2 : 0) << " ";
}
}
cout << endl;
}
return 0;
}