原题链接:https://cpc.csgrandeur.cn/csgoj/problemset/problem?pid=1195
题目内容
Description
在一个n × m的棋盘上有n行m列共n × m个格子,其中有且只有一个格子是空白的,其余n × m − 1个格子上各有一个棋子. 每一步可以将空白的格子上、下、左、右相邻的格子上的棋子移动到空白格子上.
我们需要计算将某个位置的棋子移动到指定位置的最小步数.
Input
包含不超过1000组测试数据.
每组测试数据包括8个整数n, m, x, y, r1, c1, r2, c2,表示棋盘的大小为n × m,第x行y列的格子是空白的,需要将第r1行c1列的棋子移动到第r2行c2列.
2 ≤ n ≤ 108,2 ≤ m ≤ 108.
Output
输出将棋子移动到指定位置的最小步数.
Sample Input
2 2 1 2 1 1 1 2
2 3 2 2 1 1 2 3
Sample Output
1
8
解题思路
下面会将空白的部分称为空白格,最初的格子称为初始块
-
首先明确一件事:假设空白格在初始块旁边,当空白格在当前移动方向的反方向时,移动所需的步数为5,当所在位置与移动方向垂直时,步数为3,若正好在要移动的方向,则步数为1,如图:
-
其次,阶梯型的移动方式所需步数一定比直线型移动要少。因为每次移动,初始块都会与空白格交换位置,若进行直线型移动,那么每次移动后到下一格都需要 5 步(原因如上),而阶梯型移动能保证移动方向与块的位置垂直,故只需要 3 步。
-
由以上两点可得,目标块最佳的移动方案应该是先阶梯型移动,再直线移动到达终点。
-
空白格的移动路径只需直接计算横纵距离绝对值之和即可,若出现需要穿过目标块的移动,则需要在步数上 +2。
-
至于阶梯型移动应该先横向走还是先纵向走,可以两种都模拟一遍,不用细想。
Ac代码
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, m, x, y, r1, c1, r2, c2;
int d_x, d_y, dir_x, dir_y, tx, ty;
int step, tmp_step;
while (cin >> n >> m >> x >> y >> r1 >> c1 >> r2 >> c2) {
dir_x = dir_y = 0;
d_x = r2 - r1, d_y = c2 - c1;
if (d_x < 0)
d_x = -d_x, dir_x = -1;
else if (d_x > 0)
dir_x = 1;
if (d_y < 0)
d_y = -d_y, dir_y = -1;
else if (d_y > 0)
dir_y = 1;
if (d_x == 0) { // 同行
tx = r1, ty = c1 + dir_y;
step = abs(x - tx) + abs(y - ty) + d_y * 5 - 4;
if (x == r1 && (y - c1) * dir_y < 0) step += 2;
} else if (d_y == 0) { // 同列
tx = r1 + dir_x, ty = c1;
step = abs(x - tx) + abs(y - ty) + d_x * 5 - 4;
if (y == c1 && (x - r1) * dir_x < 0) step += 2;
} else {
// 先纵向走
tx = r1 + dir_x, ty = c1;
step = abs(x - tx) + abs(y - ty) + min(d_x, d_y) * 3 * 2 - 2 + abs(d_x - d_y) * 5;
if (y == c1 && (x - r1) * dir_x < 0) step += 2;//空白格需要穿过目标块
if (d_x > d_y) step -= 2;//这种情况下阶梯型移动其实还剩最后一步
// 先横向走
tx = r1, ty = c1 + dir_y;
tmp_step = abs(x - tx) + abs(y - ty) + min(d_x, d_y) * 6 - 2 + abs(d_x - d_y) * 5;
if (x == r1 && (y - c1) * dir_y < 0) tmp_step += 2;//空白格需要穿过目标块
if (d_x < d_y) tmp_step -= 2;
step = min(step, tmp_step);
}
cout << step << endl;
}
}