【ACWing】2135. 马步距离(配数学证明)

题目地址:

https://www.acwing.com/problem/content/description/2137/

在国际象棋和中国象棋中,马的移动规则相同,都是走“日”字,我们将这种移动方式称为马步移动。如下图所示,从标号为 0 0 0的点出发,可以经过一步马步移动达到标号为 1 1 1的点,经过两步马步移动达到标号为 2 2 2的点。
在这里插入图片描述
任给平面上的两点 p p p s s s,它们的坐标分别为 ( x p , y p ) (x_p,y_p) (xp,yp) ( x s , y s ) (x_s,y_s) (xs,ys),其中, x p , y p , x s , y s x_p,y_p,x_s,y_s xp,yp,xs,ys均为整数。从 ( x p , y p ) (x_p,y_p) (xp,yp)出发经过一步马步移动可以达到 ( x p + 1 , y p + 2 ) , ( x p + 2 , y p + 1 ) , ( x p + 1 , y p − 2 ) , ( x p + 2 , y p − 1 ) , ( x p − 1 , y p + 2 ) , ( x p − 2 , y p + 1 ) , ( x p − 1 , y p − 2 ) , ( x p − 2 , y p − 1 ) (x_p+1,y_p+2),(x_p+2,y_p+1),(x_p+1,y_p−2),(x_p+2,y_p−1),(x_p−1,y_p+2),(x_p−2,y_p+1),(x_p−1,y_p−2),(x_p−2,y_p−1) (xp+1,yp+2),(xp+2,yp+1),(xp+1,yp2),(xp+2,yp1),(xp1,yp+2),(xp2,yp+1),(xp1,yp2),(xp2,yp1)。假设棋盘充分大,并且坐标可以为负数。现在请你求出从点 p p p到点 s s s至少需要经过多少次马步移动?

输入格式:
只包含 4 4 4个整数,它们彼此用空格隔开,分别为 x p , y p , x s , y s x_p,y_p,x_s,y_s xp,yp,xs,ys

输出格式:
输出一个整数,表示从点 p p p到点 s s s至少需要经过的马步移动次数。

数据范围:
− 1 0 7 < x p , y p , x s , y s < 1 0 7 −10^7<x_p,y_p,x_s,y_s<10^7 107<xp,yp,xs,ys<107

由对称性,步数与从 ( 0 , 0 ) (0,0) (0,0) ( x , y ) = ( ∣ x p − x s ∣ , ∣ y p − y s ∣ ) (x,y)=(|x_p-x_s|,|y_p-y_s|) (x,y)=(xpxs,ypys)的步数相同,进一步地,若 x < y x<y x<y,步数与到 ( y , x ) (y,x) (y,x)也相同。所以问题转化为从 ( 0 , 0 ) (0,0) (0,0) ( x , y ) , x ≥ y (x,y), x\ge y (x,y),xy的最小步数是多少,设步数为 f ( x , y ) f(x, y) f(x,y)。我们可以先考虑步数比较少的情形,试图找出规律。我们只关注第一象限中 x ≥ y x\ge y xy的区域:
在这里插入图片描述
这个区域,又可以进一步细化为 x ≥ 2 y x\ge 2y x2y的区域和 2 y > x ≥ y 2y>x\ge y 2y>xy的区域,前者我们称为 1 1 1号区域,而对于后者,在图中我们可以发现,当 2 y > x ≥ y 2y>x\ge y 2y>xy的时候,由于在 x ≥ y x\ge y xy的区域内,当 x ≥ 5 x\ge 5 x5的时候,有 f ( x , y ) = f ( x + 1 , y − 1 ) f(x, y)=f(x+1, y-1) f(x,y)=f(x+1,y1),所以我们可以从 ( x , y ) (x,y) (x,y)这个点向右下方走,直到走到 1 1 1号区域为止,所以本质上我们只需要对 1 1 1号区域求解即可。
在这里插入图片描述
接下来只考虑 1 1 1号区域。容易发现,当 x = 2 y x=2y x=2y的时候, f ( x , y ) = f ( 2 y , y ) = y f(x,y)=f(2y,y)=y f(x,y)=f(2y,y)=y,并且 f ( 2 y , y − d ) = y + d % 2 f(2y,y-d)=y+d\%2 f(2y,yd)=y+d%2,即 1 1 1号区域内,当 x x x是偶数的时候,有 f ( x , y ) = x / 2 + ( x / 2 − y ) % 2 f(x,y)=x/2+(x/2-y)\%2 f(x,y)=x/2+(x/2y)%2;当 x x x是奇数的时候,由于 f ( x , y ) = f ( x + 1 , y + 1 ) f(x,y)=f(x+1,y+1) f(x,y)=f(x+1,y+1),所以有 f ( x , y ) = ( x + 1 ) / 2 + ( ( x + 1 ) / 2 − ( y + 1 ) ) % 2 f(x,y)=(x+1)/2+((x+1)/2-(y+1))\%2 f(x,y)=(x+1)/2+((x+1)/2(y+1))%2,可以验证 y = 0 y=0 y=0的时候公式也成立。此外还有几个特殊情况需要特判,分别是 f ( 0 , 0 ) = 0 , f ( 1 , 0 ) = 3 , f ( 2 , 2 ) = 4 f(0,0)=0,f(1, 0)=3, f(2,2)=4 f(0,0)=0,f(1,0)=3,f(2,2)=4。后两个特例的产生原因是路径会经过 x ≥ y x\ge y xy这个区域之外。

简要证明一下。我们只考虑 x ≥ y x\ge y xy的区域。首先暴力解出 f ( x , y ) ≤ 4 f(x,y)\le 4 f(x,y)4的区域,这一步可以BFS来做。
先证明不在 1 1 1号区域的情形,这个可以用数学归纳法来考虑,对 f ( x , y ) f(x,y) f(x,y)进行归纳,并且假设最短路径可以完全含在 x ≥ y x\ge y xy的区域里。对于某个 f ( x , y ) ≥ 5 f(x,y)\ge 5 f(x,y)5的格子,设 k = f ( x , y ) k=f(x,y) k=f(x,y),那么由归纳假设,对于最少步数是 k − 1 k-1 k1的点是成立上面的命题的,那么对于 ( x , y ) (x,y) (x,y)这个点,其必然是从某个最少步数是 k − 1 k-1 k1的点扩展而来,比如是从 ( a , b ) (a,b) (a,b)扩展而来,找到最右下角的那个 ( a , b ) (a,b) (a,b),这样这个 ( a , b ) (a,b) (a,b)能走到 ( x + 1 , y − 1 ) (x+1,y-1) (x+1,y1),所以 f ( x + 1 , y − 1 ) = f ( x , y ) f(x+1,y-1)=f(x,y) f(x+1,y1)=f(x,y)
再证明 1 1 1号区域的情形,容易证明 x = 2 y x=2y x=2y这条直线上, f ( x , y ) = y f(x,y)=y f(x,y)=y。先用数学归纳法证明 x x x为偶数的情形,可以类似上面的方法证明出 f ( x , y ) = f ( x , y − 2 ) = f ( x , y − 4 ) = . . . f(x,y)=f(x,y-2)=f(x,y-4)=... f(x,y)=f(x,y2)=f(x,y4)=...。接下来我们两列两列考虑,在图中是可以发现规律的,对于 x x x是偶数的那一列,从上到下是 f ( 2 y , y ) , f ( 2 y , y ) + 1 , f ( 2 y , y ) , f ( 2 y , y ) + 1 , . . . f(2y, y), f(2y,y)+1,f(2y, y), f(2y,y)+1,... f(2y,y),f(2y,y)+1,f(2y,y),f(2y,y)+1,...,对于 x x x是奇数的那一列,最上面的数字可以由 f ( x , y ) = f ( x + 1 , y + 1 ) f(x,y)=f(x+1, y+1) f(x,y)=f(x+1,y+1)定出,然后向下依次是 f ( 2 y , y ) , f ( 2 y , y ) + 1 , f ( 2 y , y ) , f ( 2 y , y ) + 1 , . . . f(2y, y), f(2y,y)+1,f(2y, y), f(2y,y)+1,... f(2y,y),f(2y,y)+1,f(2y,y),f(2y,y)+1,...,规律一样。接下来可以用数学归纳法证明这个两列的规律。先证明两列两列所需步数是单调上升的,这一点比较容易,接下来可以按照马的走法模拟一遍,即可证明。

代码如下:

#include <iostream>
using namespace std;

inline long f(long x, long y) {
    if (!x && !y) return 0;
    if (x == 1 && y == 0) return 3;
    if (x == 2 && y == 2) return 4;
    if (y <= x - y) {
        if (x % 2 == 0) return x / 2 + (x / 2 - y) % 2;
        else return (x + 1) / 2 + ((x + 1) / 2 - (y + 1)) % 2;
    }
    
    long d = (2 * y - x) / 3;
    if (!d) d = 1;
    x += d, y -= d;
    return f(x, y);
}

int main() {
    long x, y, a, b;
    scanf("%ld%ld%ld%ld", &x, &y, &a, &b);
    long c = abs(x - a), d = abs(y - b);
    if (c < d) swap(c, d);
    printf("%ld\n", abs(f(c, d)));
    return 0;
}

时空复杂度 O ( 1 ) O(1) O(1)

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用回溯法求解马步问题的 C 代码示例: ``` #include <stdio.h> #define N 8 int sol[N][N] = {0}; // 存放解决方案 int move_x[8] = {2, 1, -1, -2, -2, -1, 1, 2}; // 可以走的横向距离 int move_y[8] = {1, 2, 2, 1, -1, -2, -2, -1}; // 可以走的纵向距离 // 检查是否可以移动到坐标(x,y) int is_valid_move(int x, int y) { return (x >= 0 && x < N && y >= 0 && y < N && sol[x][y] == 0); } // 使用回溯法求解马步问题 int solve(int x, int y, int step) { int i, next_x, next_y; if (step == N*N) { return 1; // 已经找到解决方案 } for (i = 0; i < 8; i++) { next_x = x + move_x[i]; next_y = y + move_y[i]; if (is_valid_move(next_x, next_y)) { sol[next_x][next_y] = step + 1; if (solve(next_x, next_y, step+1)) { return 1; } sol[next_x][next_y] = 0; // 撤回步骤 } } return 0; // 无解 } int main() { int i, j; int start_x = 0, start_y = 0; printf("请输入起始位置的坐标(0~7):\n"); scanf("%d%d", &start_x, &start_y); if (solve(start_x, start_y, 0)) { printf("解决方案:\n"); for (i = 0; i < N; i++) { for (j = 0; j < N; j++) { printf("%2d ", sol[i][j]); } printf("\n"); } } else { printf("无解!\n"); } return 0; } ``` 使用回溯法求解马步问题的思路是,从指定的起始位置开始,依次尝试可以移动的8个方向,如果可以移动到一个新位置,则标记该位置已经被访问过,并递归继续往下搜索,直到找到一个解决方案,或者搜索到无解为止。如果没有找到解决方案,则需要撤回上一步,回溯到之前的状态,尝试下一个方向。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值