这里写自定义目录标题
更多精彩内容
这里是带你游历编程世界的Dashcoding编程社,我是Dash/北航硕士/ICPC区域赛全国排名30+/给你呈现我们眼中的世界!
256题算法特训课,帮你斩获大厂60W年薪offer
原题
米哈游校招真题棋盘
B站动画详解
问题分析
这道题目描述了一个循环棋盘的最短路径问题。棋盘由 n × m n \times m n×m 的方格组成,四周是循环连接的,即在棋盘的边缘,可以直接穿越到另一侧。例如,如果探险家位于 ( x , m ) (x, m) (x,m),他可以一步移动到 ( x , 1 ) (x, 1) (x,1),这种特殊的循环连接性使得路径计算变得复杂。题目要求我们计算从起点 A A A 到中途点 B B B,再从 B B B 到终点 C C C 的最短路径。
思路分析
在这道题目中,由于棋盘具有循环特性,因此在计算两点之间的最短距离时,需要考虑两种情况:
- 直接距离:从一个点沿着棋盘直接移动到另一个点,不涉及循环连接。
- 循环距离:通过棋盘的边缘进行循环连接,从而实现更短的路径。
具体来说,对于任意两个点 ( x 1 , y 1 ) (x_1, y_1) (x1,y1) 和 ( x 2 , y 2 ) (x_2, y_2) (x2,y2),我们可以分别计算横向和纵向的最短距离。横向的最短距离是以下两个值的较小者:
- ∣ x 1 − x 2 ∣ |x_1 - x_2| ∣x1−x2∣ (直接横向移动的距离)
- n − ∣ x 1 − x 2 ∣ n - |x_1 - x_2| n−∣x1−x2∣ (通过循环连接实现的横向距离)
类似地,纵向的最短距离是以下两个值的较小者:
- ∣ y 1 − y 2 ∣ |y_1 - y_2| ∣y1−y2∣ (直接纵向移动的距离)
-
m
−
∣
y
1
−
y
2
∣
m - |y_1 - y_2|
m−∣y1−y2∣ (通过循环连接实现的纵向距离)
最终的路径长度是横向距离和纵向距离的和。
算法实现
本题的算法核心是计算两点之间的循环距离,并通过累加每段路径的最短距离得到总路径长度。为此,我们编写了一个函数 get_dist,它用于计算两个点之间的最短距离。该函数通过比较直接距离和循环距离,选取其中较小者。
整个流程分为以下几步:
读取输入的
n
n
n 和
m
m
m 以及点
A
A
A、
B
B
B、
C
C
C 的坐标。
分别计算
A
→
B
A \rightarrow B
A→B 和
B
→
C
B \rightarrow C
B→C 的最短距离,并累加得到最终结果。
代码详解
标准代码程序
C++代码
#include <bits/stdc++.h>
using namespace std;
typedef pair<int, int> PII;
PII a[5];
int n, m, s;
int get_dist(PII x, PII y) {
int x_d = min(abs(x.first - y.first), n - abs(x.first - y.first)); // 计算横向距离
int y_d = min(abs(x.second - y.second), m - abs(x.second - y.second)); // 计算纵向距离
return x_d + y_d; // 返回总距离
}
int main() {
cin >> n >> m;
for (int i = 1; i <= 3; i++) {
cin >> a[i].first >> a[i].second;
}
s += get_dist(a[1], a[2]); // 计算 A 到 B 的距离
s += get_dist(a[2], a[3]); // 计算 B 到 C 的距离
cout << s; // 输出总距离
}
Java代码
import java.util.Scanner;
public class CircularGrid {
static class Point {
int x, y;
Point(int x, int y) {
this.x = x;
this.y = y;
}
}
public static int getDistance(Point p1, Point p2, int n, int m) {
int xDist = Math.min(Math.abs(p1.x - p2.x), n - Math.abs(p1.x - p2.x));
int yDist = Math.min(Math.abs(p1.y - p2.y), m - Math.abs(p1.y - p2.y));
return xDist + yDist;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(), m = sc.nextInt();
Point a = new Point(sc.nextInt(), sc.nextInt());
Point b = new Point(sc.nextInt(), sc.nextInt());
Point c = new Point(sc.nextInt(), sc.nextInt());
int distance = getDistance(a, b, n, m) + getDistance(b, c, n, m);
System.out.println(distance);
}
}
Python代码
def get_distance(p1, p2, n, m):
x_dist = min(abs(p1[0] - p2[0]), n - abs(p1[0] - p2[0]))
y_dist = min(abs(p1[1] - p2[1]), m - abs(p1[1] - p2[1]))
return x_dist + y_dist
def main():
n, m = map(int, input().split())
a = tuple(map(int, input().split()))
b = tuple(map(int, input().split()))
c = tuple(map(int, input().split()))
distance = get_distance(a, b, n, m) + get_distance(b, c, n, m)
print(distance)
if __name__ == "__main__":
main()
Javascript代码
function getDistance(p1, p2, n, m) {
const xDist = Math.min(Math.abs(p1[0] - p2[0]), n - Math.abs(p1[0] - p2[0]));
const yDist = Math.min(Math.abs(p1[1] - p2[1]), m - Math.abs(p1[1] - p2[1]));
return xDist + yDist;
}
function main() {
const [n, m] = prompt().split(' ').map(Number);
const a = prompt().split(' ').map(Number);
const b = prompt().split(' ').map(Number);
const c = prompt().split(' ').map(Number);
const distance = getDistance(a, b, n, m) + getDistance(b, c, n, m);
console.log(distance);
}
main();
复杂度分析
时间复杂度
计算从 A A A 到 B B B 再到 C C C 的最短路径,涉及两次距离计算:
- 从 A A A 到 B B B
- 从 B B B 到 C C C
每次距离计算的复杂度为 O ( 1 ) O(1) O(1),因为我们只是对坐标进行简单的绝对值运算和比较。整个算法的时间复杂度为 O ( 1 ) O(1) O(1)。
空间复杂度
本算法使用了常数个变量来存储坐标和计算结果,额外的空间仅用于存储坐标信息和中间结果,因此空间复杂度为 O ( 1 ) O(1) O(1)。
总结
这道题目考察的是在具有循环边界的棋盘上进行最短路径的计算问题。由于棋盘的特殊性质,即在水平方向和竖直方向上都存在循环连接,这使得从一个位置移动到另一个位置时可以选择两种路径:直接路径和穿越边界的路径。因此,问题的核心在于计算两点之间的最短距离时,如何合理利用循环边界。
解决方案通过数学方法,基于坐标差值计算最短距离。对于每个方向(横向或纵向),直接距离和跨越边界的距离取最小值,然后将两个方向的最小值相加得到总步数。由于问题规模较大( 1 ≤ n , m ≤ 1 0 9 1 \leq n, m \leq 10^9 1≤n,m≤109),算法需要在常数时间内完成计算。
整个过程分为两步:
- 计算从起点 A A A 到中间点 B B B 的最短距离。
- 计算从中间点 B B B 到终点 C C C 的最短距离。
该问题的关键是理解循环边界的特性,并且将其应用于最短路径的计算中。总体来说,问题复杂度较低,适合通过数学推导和简单的几何计算来解决。