洛谷——P1002 过河卒

本文详细解析了动态规划中状态转移方程的推导过程,以及如何通过理解状态转移方程实现空间复杂度的优化,从二维数组压缩到一维数组,提供了完整的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原题链接

一道比较基础的动态规划的题目,这里细致的讲解一下如何得到状态转移方程以及如何对空间进行压缩。

首先,不难想到,用一个二维数组dp表示状态,那么dp[i][j]就可以表示到达第i行第j列所有的方案总数。因为棋子到达(i,j)位置的方式只有两种,一种是从(i-1,j)向下移动一格,一种是从(i,j-1)向右移动一格。所以,到达(i,j)的方案总数就是(i-1,j)和(i,j-1)的方案数相加。即状态转移方程:
dp[i][j]=dp[i-1][j]]+dp[i][j-1]
但是要注意,如果马无法到达(1,1)位置,那么边界dp[1][1]应该初始化为1。而实际上在循环的过程中dp[1][1]=dp[1][0]+dp[0][1]=0,这就直接导致最终输出的答案为零。因此需要加个特判或是直接写成dp[i][j] = max(dp[i - 1][j] + dp[i][j - 1], dp[i][j])

这里需要注意三点:其一是答案会很大,所以该数组声明为unsigned long long。

其二是原点在(0,0),这样会有越界的危险,所以将输入的四个整数统一加一,这样坐标原点就变成了(1,1)。

其三是马能够到达的点的方案数为0。

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 30;
int m, n, x, y;
unsigned long long dp[N][N];
bool board[N][N];
// 储存马移动的八个方向
int dx[] = { -2,-1,1,2,2,1,-1,-2 };
int dy[] = { -1,-2,-2,-1,1,2,2,1 };
int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> m >> n;
	cin >> x >> y;
	m += 1, n += 1, x += 1, y += 1;
	// 将马能够到达的地方用board数组记录
	board[x][y] = 1;
	for (int i = 0; i < 8; i++) {
		int tx = x + dx[i], ty = y + dy[i];
		if (tx >= 0 && tx <= m && ty >= 0 && ty <= n)
			board[tx][ty] = 1;
	}
	if (board[1][1]) {
		cout << 0;
		return 0;
	}
	// 边界初始化
	dp[1][1] = 1;
	for (int i = 1; i <= m; i++) {
		for (int j = 1; j <= n; j++) {
			if (board[i][j])
				continue;
			else {
				dp[i][j] = max(dp[i - 1][j] + dp[i][j - 1], dp[i][j]);
			}
				
		}
	}
	cout << dp[m][n];
	return 0;
}

既然写出来了,我们就尝试一下是否可以压缩空间。
因为状态转移方程dp[i][j] = max(dp[i - 1][j] + dp[i][j - 1], dp[i][j]) 每一次只是用到上一行储存的值和这一行储存的值,那么是否可以数组行数是否可以只开两行呢?

答案是可以的。这里我们需要做的是把i,i-1用0或1代替,因为数组只有两行。因为i每次只增加1,也就是从偶数到奇数再到偶数再到奇数…那么可以尝试用奇偶性对i变换:对i取2的余数。这样,就可以把数组压缩至2行。

由于压缩空间,带来了一些问题。第一是要将马能够到达的位置在dp数组映射后的位置一定要清0,。因为空间压缩会导致dp[i%2][j]在更新前储存别的值,不一定为0。

第二是上文的状态转移方程dp[i][j] = max(dp[i - 1][j] + dp[i][j - 1], dp[i][j]) 这里不能够用max,原因也是空间压缩会导致dp[i][j]在更新前储存别的值,未必会比dp[i - 1][j] + dp[i][j - 1]小。所以对于特殊状态,也就是i=1和j=1的情况需要特判。当然,此时的状态转移方程就变成 dp[i % 2][j] = dp[(i - 1) % 2][j] + dp[i % 2][j - 1]

对于这个地方不太理解的,可以手动模拟一下测试用例,就会明白其中的原理。

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 30;
int m, n, x, y;
unsigned long long dp[2][N];
bool board[N][N];
int dx[] = { -2,-1,1,2,2,1,-1,-2 };
int dy[] = { -1,-2,-2,-1,1,2,2,1 };
int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> m >> n;
	cin >> x >> y;
	m += 1, n += 1, x += 1, y += 1;
	board[x][y] = 1;
	for (int i = 0; i < 8; i++) {
		int tx = x + dx[i], ty = y + dy[i];
		if (tx >= 0 && tx <= m && ty >= 0 && ty <= n)
			board[tx][ty] = 1;
	}
	if (board[1][1]) {
		cout << 0;
		return 0;
	}
	dp[1][1] = 1;
	for (int i = 1; i <= m; i++) {
		for (int j = 1; j <= n; j++) {
			if (board[i][j])
				dp[i % 2][j] = 0;
			else if (i == 1 && j == 1)
				continue;
			else 
				dp[i % 2][j] = dp[(i - 1) % 2][j] + dp[i % 2][j - 1];
			

		}
	}
	cout << dp[m % 2][n];
	return 0;
}

那么我们是否能够用1维数组再次优化呢?答案是可以的。
上文写到dp[i%2][j]在更新前储存别的值

那么思考一下,储存的是什么值。很显然,是上一次计算的值,也就是到达(i-1,j)位置的方案总数。所以很容易就压缩到一维数组, 状态转移方程为:
dp[i]+=dp[i-1]

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 30;
int m, n, x, y;
unsigned long long dp[N];
bool board[N][N];
int dx[] = { -2,-1,1,2,2,1,-1,-2 };
int dy[] = { -1,-2,-2,-1,1,2,2,1 };
int main()
{
	ios_base::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> m >> n;
	cin >> x >> y;
	m += 1, n += 1, x += 1, y += 1;
	board[x][y] = 1;
	for (int i = 0; i < 8; i++) {
		int tx = x + dx[i], ty = y + dy[i];
		if (tx >= 0 && tx <= m && ty >= 0 && ty <= n)
			board[tx][ty] = 1;
	}
	if (board[1][1]) {
		cout << 0;
		return 0;
	}
	dp[1] = 1;
	for (int i = 1; i <= m; i++) {
		for (int j = 1; j <= n; j++) {
			if (board[i][j])
				dp[j] = 0;
			else if (i == 1 && j == 1)
				continue;
			else
				dp[j] += dp[j - 1];
			

		}
	}
	cout << dp[n];
	return 0;
}

如果有帮助还请各位小伙伴们点个赞哦~

以下是C语言p1002过河的代码实现和注释解析: ```c #include <stdio.h> #define MAX 21 // 定义最大值 int main() { int n, m, xh, yh, i, j, k; long long map[MAX][MAX] = {0}; // 定义地图,初始化为0 scanf("%d %d %d %d", &n, &m, &xh, &yh); // 输入信息 map[xh][yh] = -1; // 标记障碍物 for (k = 0; k < 3; k++) { // 遍历3个方向 switch (k) { case 0: i = 2; j = 1; break; // 方向1 case 1: i = 1; j = 2; break; // 方向2 case 2: i = 1; j = 1; break; // 方向3 } if (xh - i >= 0 && yh - j >= 0) { // 判断是否越界 map[xh - i][yh - j] = -1; // 标记障碍物 } if (xh + i <= n && yh + j <= m) { // 判断是否越界 map[xh + i][yh + j] = -1; // 标记障碍物 } } map[0][0] = 1; // 起点为1 for (i = 0; i <= n; i++) { // 遍历行 for (j = 0; j <= m; j++) { // 遍历列 if (map[i][j] != -1) { // 判断是否为障碍物 if (map[i - 1][j] != -1) { // 上可通过 map[i][j] += map[i - 1][j]; // 累加上方格子的值 } if (map[i][j - 1] != -1) { // 左可通过 map[i][j] += map[i][j - 1]; // 累加左方格子的值 } } } } printf("%lld\n", map[n][m]); // 输出结果 return 0; } ``` 注释解析: 1. 定义了一个二维数组`map`,用于存储地图信息,初始化为0。 2. 输入了4个整数,分别为地图的行数`n`、列数`m`、的初始位置`xh`、`yh`。 3. 标记了障碍物,即不能到达的位置。 4. 遍历了3个方向,分别为向上走2步、向左走1步;向上走1步、向左走2步;向上走1步、向左走1步。如果可以到达该位置,则标记为障碍物。 5. 起点的值为1,表示只有一种走法。 6. 遍历整个地图,如果该位置不是障碍物,则累加上方格子和左方格子的值。 7. 输出结果,即终点的值。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值