题面

问题描述
  小明每天都要练功,练功中的重要一项是梅花桩。
  小明练功的梅花桩排列成 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 + ii 是从 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

_我已经从中二毕业了
7.9k 声望235 粉丝

不搞前端会死星人。