"こんにちは!" 我是Zac!今天要给大家讲一个关于一匹不听话的马,和两个程序员相爱相杀的故事。准备好了吗?系好安全带,我们要发车了!
先上题目吧:timu传送门
题目
题目描述
有一个 n×m 的棋盘,在某个点 (x,y) 上有一个马,要求你计算出马到达棋盘上任意一个点最少要走几步。
输入格式
输入只有一行四个整数,分别为 n,m,x,y。
输出格式
一个 n×m 的矩阵,代表马到达某个点最少要走几步(不能到达则输出 −1)。
输入输出样例
输入#1
3 3 1 1
输出#1
0 3 2
3 -1 1
2 1 4
说明/提示
数据规模与约定
对于全部的测试点,保证 1≤x≤n≤400,1≤y≤m≤400。
2022 年 8 月之后,本题去除了对输出保留场宽的要求。为了与之兼容,本题的输出以空格或者合理的场宽分割每个整数都将判作正确。
第一幕:天真程序员的幻想
"这不就是个简单的搜索题嘛!" 我对着屏幕傻笑,仿佛已经看到了AC的曙光。毕竟我可是刚学会DFS的"大佬"啊!(后来证明是"大老",又大又老又菜)
DFS思路:让马儿自由飞翔
DFS的思路就像放养一匹野马:
1. 从起点出发,随便选一个方向跳(日字有8种跳法呢)
2. 跳到新位置后继续随便跳
3. 记录到达每个位置的最小步数
4. 如果发现更短的路径就更新
5. 直到...呃...直到所有可能性都试完?
// 此处的DFS代码,实在太丢人了
#include <iostream>
#include <climits>
using namespace std;
const int N = 405;
const int dir[8][2] = {
{-2, -1}, {-2, 1},
{-1, -2}, {-1, 2},
{1, -2}, {1, 2},
{2, -1}, {2, 1}
};
int n, m, x, y;
int dis[N][N];
void dfs(int i, int j, int s) {
if (i < 0 || i >= n || j < 0 || j >= m) return ;
if (s >= dis[i][j] && dis[i][j] != -1) return ;
dis[i][j] = s;
for (int k = 0; k < 8; k++) {
int ni = i + dir[k][0];
int nj = j + dir[k][1];
dfs(ni, nj, s + 1);
}
}
int main() {
cin >> n >> m >> x >> y;
x--;
y--;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
dis[i][j] = -1;
}
}
dfs(x, y, 0);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cout << dis[i][j];
if (j < m - 1) cout << " ";
}
cout << endl;
}
return 0;
}
// 就像我的初恋一样不堪回首(我有初恋吗?)
第二幕:现实的重锤
当我看到400x400的棋盘时,我的DFS就像:
- 一匹喝醉的马
- 在迷宫里瞎转悠
- 时不时撞墙
- 还总走回头路
DFS为什么不行?
1. 递归深度太大,容易爆栈(马儿累死了)
2. 会重复访问同一个位置无数次(马儿迷路了)
3. 无法保证第一次访问就是最短路径(马儿绕远路)
4. 时间复杂度爆炸(马儿跑不动了)
评测机内心OS:"这孩子怕不是个傻子吧?"
第三幕:救世主BFS登场
就在我准备放弃治疗的时候,BFS像超级英雄一样出现了!它带着队列这个秘密武器,准备教DFS怎么做人。
BFS思路:训练有素的马术表演
BFS的工作方式就像专业的马术表演:
1. 让马儿们排好队(队列初始化)
2. 从起点开始,记录步数为0(起点不需要跳)
3. 每次取出队首的马儿,让它尝试所有8种跳法
4. 对于每个新位置:
- 如果是第一次到达,记录步数并入队
- 否则跳过(因为BFS保证第一次到达就是最短路径)
5. 直到队列为空(所有可达位置都访问过)
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
// 此处是优雅的BFS代码
const int N = 405;
const int dr[8] = {-2, -2, -1, -1, 1, 1, 2, 2};
const int dc[8] = {-1, 1, -2, 2, -2, 2, -1, 1};
int n, m, x, y;
int d[N][N];
queue<pair<int, int>> q;
void bfs() {
while (!q.empty()) {
int r = q.front().first;
int c = q.front().second;
q.pop();
for (int k = 0; k < 8; k++) {
int nr = r + dr[k];
int nc = c + dc[k];
if (nr >= 0 && nr < n && nc >= 0 && nc < m && d[nr][nc] == -1) {
d[nr][nc] = d[r][c] + 1;
q.push({nr, nc});
}
}
}
}
int main() {
cin >> n >> m >> x >> y;
x--;
y--;
memset(d, -1, sizeof(d));
d[x][y] = 0;
q.push({x, y});
bfs();
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cout << d[i][j];
if (j < m - 1) cout << " ";
}
cout << endl;
}
return 0;
}
// 就像我的现任女友一样完美(我有女友吗???)
为什么BFS这么秀?
1. **队列管理**:让马儿们乖乖排队,不插队不抢跑
2. **层次遍历**:保证先处理距离近的点,再处理远的点
3. **最短路径**:第一次到达就是最短路径,不用绕弯子
4. **效率保障**:每个点只处理一次,时间复杂度O(nm)
血泪教训总结
1. DFS适用场景:
- 需要探索所有可能路径的问题
- 拓扑排序、连通分量等问题
- 就像青春期的恋爱:冲动、盲目、容易陷入太深
2. BFS适用场景:
- 无权图的最短路径问题
- 层次遍历问题
- 就像成熟后的婚姻:稳重、有序、一步一个脚印
3. 评测机真相:
- 它就像严格的丈母娘
- 永远知道你哪里不行
- 但也会在你做对时给你满分
最后送给大家一句忠告:看到"最短路径"四个字,请直接选择BFS,除非你想像我一样体验社会性死亡!
"Au revoir!" 我是Zac,我们下次再见!(如果我还活着从这道题里爬出来的话)