【洛谷P1002】题解

文章介绍了使用动态规划方法解决从起点到终点的最短路径问题,讨论了初始条件、状态转移方程以及如何处理马的控制点。通过C/C++代码演示了解决方案,并分析了时间复杂度和空间复杂度,提出了优化空间复杂度的可能性。
摘要由CSDN通过智能技术生成

千里之行,始于足下,让我们从今天开始,走上通向神犇的道路!
同志们,加油!
任何一个伟大的思想,都有一个微不足道的开始。

声明:理解为重,代码次之!

原题

前言

真没想到洛谷主题库题目的难度涨得这么快啊 o r z \rm orz orz
这篇题解会讲述C/C++的做法(Python难一点就不会了😭)

思路分析

什么!!!标签竟有 动态规划,dp \colorbox{3498db}{\color{FFFFFF}动态规划,dp} 动态规划,dp!!!这么难!!!

仔细一看,其实这是一道小学奥数题,就是在许多交叉的线中找到到达终点的最短路线。老师可能教过例如“对角相连”的方法,但是这到底是为什么呢?让我们继续往下看。

思考最简单的方式,如果枚举往右或往下然后回溯搜索,就会超市,得到TLE。先考虑一个简化版的问题:如果那个马不存在,从左上角到右下角一共有多少种走法?
定义一个二维数组 f f f,记录从原点走到坐标 ( i , j ) (i,j) (i,j) 的方法总数是 f i , j f_{i,j} fi,j。当卒从起点开始,笔直往右或者笔直往下,无论走多远都只有唯一的一种走法(因为一旦偏移就再也回不去了),所以当 k ⩾ 0 k\geqslant0 k0 时, f 0 , k = f k , 0 = 1 f_{0,k}=f_{k,0}=1 f0,k=fk,0=1,这就是递推的初始条件。如何到点 ( 1 , 1 ) (1,1) (1,1) 呢?要么是从点 ( 0 , 1 ) (0,1) (0,1) 往下走一格,要么是从点 ( 1 , 0 ) (1,0) (1,0) 往右走一格,因此到 ( 1 , 1 ) (1,1) (1,1) 的方案数量就是到 ( 0 , 1 ) (0,1) (0,1) 的数量加上到 ( 1 , 0 ) (1,0) (1,0) 的数量,可得 f 1 , 1 = f 0 , 1 + f 1 , 0 f_{1,1} = f_{0,1} + f_{1,0} f1,1=f0,1+f1,0。可以归纳得到状态转移方程(递推式): f i , j = f i − 1 , j + f i , j − 1 ( i > 0 , j > 0 ) f_{i,j}=f_{i-1,j}+f_{i,j-1}(i>0,j>0) fi,j=fi1,j+fi,j1(i>0,j>0)
有了状态转移方程,有了初始条件,就可以求出完整的没有马的 f f f 数组的值了。

如果有些点因为马的把守而不能走呢?其实也没有什么区别,只不过没办法从马的控制点转移到下一个点罢了(换句话说,马的控制点上路径数全部清空)。此外,初始条件和递推范围也都一点变化,只需要 f 0 , 0 = 1 f_{0,0}=1 f0,0=1 即可,同时递推范围就是 i ⩾ 0 , j ⩾ 0 , i ⋅ j ≠ 0 i\geqslant0,j\geqslant0,i\cdot j\not=0 i0,j0,ij=0

代码演示

因为CC++大体实现相同,所以不再写两份代码偷懒

#include <iostream>
#define MAXN 22 // C++中推荐使用:const int MAXN = 22;
using namespace std;
long long f[MAXN][MAXN] = {0};
int ctr[MAXN][MAXN], n, m, hx, hy;
int d[9][2] = {{0, 0}, {1, 2}, {1, -2}, {-1, 2}, {-1, -2}, {2, 1}, {2, -1}, {-2, 1}, {-2, -1}};
// 马的控制范围相对于马的位置的偏移量
int main() {
	cin >> n >> m >> hx >> hy; // C中用scanf("%d%d%d%d", &n, &m, &hx, &hy);
	for (int i = 0; i < 9; i++) {
		int tmpx = hx + d[i][0], tmpy = hy + d[i][1];
		if (tmpx >= 0 && tmpx <= n && tmpy >= 0 && tmpy <= m) ctr[tmpx][tmpy] = 1;
		// 判断是否在棋盘范围内并记录马的控制点
	}
	f[0][0] = 1 - ctr[0][0]; // 如果原点就是马的控制点,则初始路径就是0,否则是1
	for (int i = 0; i <= n; i++)
		for (int j = 0; j <= m; j++) {
		if (ctr[i][j]) continue; // 若这个点是控制点,则跳过
		if (i) f[i][j] += f[i - 1][j]; // 若不在横轴上就加上上面路径数
		if (j) f[i][j] += f[i][j - 1]; // 若不在纵轴上就加上左边路径数
	}
	cout << f[n][m]; // 输出答案
	return 0;
}

复杂度

时间复杂度

很明显,程序中有一个 2 2 2 层循环,每个循环都是 0 ∼ n 0 \sim n 0n 0 ∼ m 0 \sim m 0m,所以最终时间复杂度是 O ( n m ) O(nm) O(nm),可以在给定时间内通过。

空间复杂度

定义的最大的一个数组是 f f f,可以看出空间复杂度是 O ( M A X N ⋅ M A X N ) O(MAXN\cdot MAXN) O(MAXNMAXN),也是 O ( n m ) O(nm) O(nm)

优化

当然,空间复杂度是可以优化成 O ( n ) O(n) O(n) 的,但这可以说是玄学且没有用,感兴趣的读者可以自己膜拜大神

总结

学习递推开始必刷的题目,NOIP考题,稍有思维难度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三日连珠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值