题面
问题描述
小明每天都要练功,练功中的重要一项是梅花桩。
小明练功的梅花桩排列成 n 行 m 列,相邻两行的距离为 1,相邻两列的距离也为 1。
小明站在第 1 行第 1 列上,他要走到第 n 行第 m 列上。小明已经练了一段时间,他现在可以一步移动不超过 d 的距离(直线距离)。
小明想知道,在不掉下梅花桩的情况下,自己最少要多少步可以移动到目标。
输入格式
输入的第一行包含两个整数 n, m,分别表示梅花桩的行数和列数。
第二行包含一个实数 d(最多包含一位小数),表示小明一步可以移动的距离。
输出格式
输出一个整数,表示小明最少多少步可以到达目标。
样例输入
3 4
1.5
样例输出
3
评测用例规模与约定
对于 30% 的评测用例,2 <= n, m <= 20,1 <= d <= 20。
对于 60% 的评测用例,2 <= n, m <= 100,1 <= d <= 100。
对于所有评测用例,2 <= n, m <= 1000,1 <= d <= 100。
题解 1
使用宽度优先搜索,从(1,1)开始搜索。用 x 表示行,用 y 表示列。d 为用户输入的可以移动的距离
- 往下搜索
(x + d, y)
; - 往右搜索
(x, y + d)
; - 斜着搜索:从当前点 p1(从队头取出)出发,找另外一个点 p2,符合 p1p2 的距离的要小于 d*d 的点 p2 入队。
代码里使用了bool booked[N][N]
二维数组用来表示已经走过的(x, y)
点,避免重复的搜索。由于输入的步数 d 可能会很大,斜着走更加容易走到终点,把斜着走符合条件的点先入队,会避免走那些更远的点,从而达到节省时间的效果。
⚠️斜着走搜索可以访问的点时间复杂度太高,无法通过 100% 的数据。且部分答案错误,仅提供参考思路。
代码 1
#include <iostream>
#include <algorithm>
#include <cmath>
#include <queue>
#define N 10001
using namespace std;
const double minDis = sqrt(2);
int n, m;
double d;
bool booked[N][N];
struct Point {
int x, y, step;
};
void bfs () {
// d1 横竖着走最大的移动距离
// d2 斜着走最大的移动距离
int d1 = (int)d;
queue<struct Point> Q;
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
booked[i][j] = false;
struct Point p;
p.x = 1;
p.y = 1;
p.step = 0;
Q.push(p);
while (Q.size()) {
struct Point top = Q.front(); Q.pop();
if (top.x >= n && top.y >= m) {
cout << top.step << endl;
break;
}
struct Point p1, p2, p3;
// 斜着走
// 斜着走最小都需要跟号 2 的长度
if (d >= minDis) {
for (int i = 1; i <= d; i++) {
for (int j = 1; j <= d; j++) {
int temp = j * j + i * i;
if (temp <= d * d) {
struct Point p3;
p3.x = j + top.y;
p3.y = i + top.x;
if (!booked[p3.x][p3.y]) {
booked[p3.x][p3.y] = true;
p3.step = top.step + 1;
Q.push(p3);
}
}
}
}
}
p1.x = top.x + d1;
if (!booked[p1.x][top.y] && top.x < n) {
// 向下走
p1.y = top.y;
p1.step = top.step + 1;
booked[p1.x][p1.y] = true;
Q.push(p1);
}
p2.y = top.y + d1;
if (!booked[top.x][p2.y] && top.y < m) {
// 向右走
p2.x = top.x;
p2.step = top.step + 1;
booked[p2.x][p2.y] = true;
Q.push(p2);
}
}
}
int main () {
cin >> n >> m;
cin >> d;
bfs();
return 0;
}
题解 2
由于第一个解题思路斜着走搜索时间复杂度太高,经过长时间的思考(熬夜)想到另外一种解题思路:
- 先从右边至右下搜索:从头结点的
ty = y + d >= m ? m : y + d
开始搜索,头结点tx = x + i
,i
是从 0 递增到d
,新的点(tx, ty)
和头结点计算距离判断是否小于d*d
,如果是则将新点入队; - 当新的点
(tx, ty)
入队后,进入第二个循环,开始往回走,寻找以d
为半径内覆盖的最远的点(tx, ty - j)
,变量j
从 1 开始递增,直到找到新点(tx, ty - j)
,或ty - j
小于 0,则退出。
x
为当前队列头结点的成员变量,y
为当前队列头结点的成员变量,d
为用户输入的浮点数向下取整。
代码里使用了bool booked[N][N]
二维数组用来表示已经走过的(x, y)
点,避免重复的搜索。
代码 2
#include <iostream>
#include <algorithm>
#include <cmath>
#include <queue>
#define N 2000
using namespace std;
int n, m;
double d;
bool booked[N][N] = {false};
struct Point {
int x, y, step;
};
void bfs () {
int d1 = (int)d, tx, ty;
queue<struct Point> Q;
struct Point p;
p.x = 1;
p.y = 1;
p.step = 0;
Q.push(p);
while (Q.size()) {
struct Point top = Q.front(); Q.pop();
if (top.x >= n && top.y >= m) {
cout << top.step << endl;
break;
}
// 向右和右下搜索
for (int i = 0; i <= d1; i++) {
tx = top.x + i;
ty = top.y + d1 >= m ? m : top.y + d1;
if ((tx - top.x) * (tx - top.x) + (ty - top.y) * (ty - top.y) <= d * d && !booked[tx][ty]) {
struct Point p;
p.x = tx;
p.y = ty;
p.step = top.step + 1;
booked[tx][ty] = true;
Q.push(p);
}
if (i > 0) {
int j = 1;
bool found = false;
while (!found && ty - j - top.y >= 0) {
if ((tx - top.x) * (tx - top.x) + (ty - j - top.y) * (ty - j - top.y) <= d * d && !booked[tx][ty - j]) {
struct Point p;
p.x = tx;
p.y = ty - j;
p.step = top.step + 1;
booked[tx][ty - j] = true;
found = true;
break;
} else {
j++;
}
}
}
}
}
}
int main () {
cin >> n >> m;
cin >> d;
bfs();
return 0;
}
知识点
- 宽度优先搜索(bfs);
- 两点之间的距离:
d^2 = (x - x1)^2 + (y - y1)^2
。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。