P1002 [NOIP2002 普及组] 过河卒

过河卒路径计数

题目描述

棋盘上 A 点有一个过河卒,需要走到目标 B 点。卒行走的规则:可以向下、或者向右。同时在棋盘上 C 点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。

棋盘用坐标表示,A 点 (0,0)、B 点 (n,m),同样马的位置坐标是需要给出的。

现在要求你计算出卒从 A 点能够到达 B 点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。

输入格式

一行四个正整数,分别表示 B 点坐标和马的坐标。

输出格式

一个整数,表示所有的路径条数。

输入输出样例

输入 #1

6 6 3 3

输出 #1

6

说明/提示

对于 100% 的数据,1≤n,m≤20,0≤ 马的坐标 ≤20

思路:

这道题与P1255 数楼梯的思路是一致的,不过不同的是这道题是二维的,数楼梯是一维的问题,

如果对P1255 数楼梯中的一维的办法还不太理解的话,可以先去看看我之前的文章P1255 数楼梯

那么我们直接讲二维的思路了,这题要求到达从左上角的点,到达右下角的点可能的步数,同时存在有对方马的控制点,这些点我们就不可以经过。

左上角的点我们记为(0,0),右下角的点就是(x,y),注意不是(x-1,y-1),仔细观察题目的图,所以这里很容易犯错的就是建立数组时,我们要x+1和y+1,因为数组是从0开始的。我们建立一个二维数组count,count(i,j)表示从起点到(i,j)的路径数,我们初始化count(0,0) = 1,有题目可以知道过河卒只能向下和向右走,所以count(i,j)的步数只由count(i-1,j)和count(i,j-1)来决定,所以count(i,j)也就等于count(i-1,j)与count(i,j-1)之和,即count(i,j)=count(i-1,j)+ count(i,j-1) 。

同时来处理对方马的控制点,我们只需创建两个数组,代表以马的坐标为中心,各个控制点x方向和y方向的变化就可以实现,记录下马的所有控制点。在设置完所有对方马的控制点后,将这些点的count设置为0,同时当在遍历的时候如果遇到这些点,直接跳过此次循环,进入下一次循环。

最后注意一下边界的情况,边界就只有一边可以到达,左边界的只受上一格的步数影响,上边界的只受左一格的步数影响。所以当i = 0 或者 j = 0的时候,我们只需将count[i][j] = count[i][j-1] 或者 count[i][j] = count[i-1][j]就可以了。

最后以图的形式再呈现一下。 

接下来就是代码的呈现咯!

AC代码

#include<iostream>
using namespace std;
typedef long long ll;
int X[9] = { -2,-2,-1,-1,0,1,1,2,2 };//模拟对方马的控制点
int Y[9] = { -1,1,-2,2,0,-2,2,-1,1 };
void solve()
{
	int n, m, x, y;
	cin >> n >> m >> x >> y;
	ll** vec = new ll* [n+1];
	for (int i = 0; i < n+1; i++) {
		vec[i] = new ll[m+1];
	}
	for (int i = 0; i <= n; i++) {
		for (int j = 0; j <= m; j++) {
			vec[i][j] = 1;
		}
	}
	for (int i = 0; i < 9; i++) {
		if (x + X[i] >= 0 && x + X[i] <= n && y+Y[i]>= 0 && y+Y[i] <= m) {
			vec[x + X[i]][y + Y[i]] = 0;
		}
	}
	for (int i = 0; i <= n; i++) {
		for (int j = 0; j <= m; j++) {
			if (i == 0 && j == 0) { continue; }
			if (vec[i][j] == 0) { continue; }//跳过
			if (i == 0) {
				vec[i][j] = vec[i][j - 1];
			}
			if (j == 0) {
				vec[i][j] = vec[i - 1][j];
			}
			if (i != 0 && j != 0 && vec[i][j] != 0) {
				vec[i][j] = vec[i - 1][j] + vec[i][j - 1];
			}
		}
	}
	cout << vec[n][m];
    for(int i = 0;i < n+1;i++){
        delete[]vec[i];
    }
    delete[]vec;
}
int main()
{
	int t = 1;
	cin.tie(0)->ios::sync_with_stdio(false);
	while (t--) {
		solve();
	}
	return 0;
}

<think>嗯,用户需要关于NOIP 2002普及组过河问题的Java实现和解题思路。首先,我得回忆一下这个问题的具体要求。过河的问题应该是一个典型的动态规划问题,涉及到棋盘上的路径计数,并且有障碍物(比如马的攻击点)。用户可能需要状态转移方程以及具体的代码实现。 首先,我得先确定问题的描述是否正确。过河通常是从棋盘的一个点(比如原点)到另一个点(比如目标点),而中间有一些位置被马控制,不能经过。这时候需要计算子从起点到终点的路径数目,每次只能向右或向下移动。没错,这应该是一个二维动态规划的问题,其中dp[i][j]表示到达(i,j)点的路径数,状态转移方程应该是dp[i][j] = dp[i-1][j] + dp[i][j-1],前提是该点不是被马控制的点。 接下来要考虑的是如何处理马的控制点。需要标记所有被马挡住的点,这样在动态规划过程中遇到这些点就直接跳过,或者将它们的dp值设为0。同时,要注意边界条件的处理,比如第一行和第一列,如果路径被阻断,后面的点也无法到达。 然后,Java的实现需要注意数组的大小。通常棋盘可能从(0,0)到(n,m),所以数组可能需要声明为(n+1) x (m+1)的大小。此外,要考虑可能的越界问题,比如当i-1或j-1为负数时的情况,这时候需要判断是否在有效范围内。 用户可能还会关心如何处理输入,比如输入目标点的坐标和马的位置。不过根据题目要求,可能只需要核心算法部分。所以代码示例可能需要初始化棋盘,标记被马控制的点,然后填充dp数组。 另外,动态规划的初始条件是dp[0][0] = 1,如果起点没有被马控制的话。否则路径数直接为0。 现在需要检查是否有引用中的相关内容。引用[3]提到了过河题解,其中提到状态转移时,未被更新的部分保存的是上一行的答案,这可能涉及到滚动数组优化,但用户的问题可能不需要优化,直接使用二维数组更直观。不过引用[5]中的状态转移方程是使用逻辑或,这可能和当前问题不同,因为过河是路径计数,而不是布尔型的是否可达。所以可能引用中的部分内容不适用,但需要确认。 用户可能需要Java代码的结构,比如如何处理输入,初始化dp数组,标记障碍点,然后遍历每个点计算路径数。同时要考虑大数问题,因为路径数可能很大,超过int范围,所以可能需要使用long类型。 最后,需要确保代码示例正确,没有语法错误,并且符合动态规划的逻辑。比如,在遍历每个点的时候,要检查是否是障碍点,如果是的话就跳过,否则累加上面和左边的路径数。同时,边界情况如i=0或j=0时,只能从左边或上边来,这时候需要特殊处理。</think>### NOIP 2002 普及组 过河 Java 实现与解题思路 #### 问题描述 棋盘上从起点 $(0,0)$ 走到终点 $(n,m)$,每次只能向右或向下移动一格。棋盘上有马的初始位置 $(x,y)$,马控制的 $9$ 个点(包括自身)为障碍,不可经过。求所有可行路径数。 --- #### 核心思路 1. **动态规划定义** 设 $dp[i][j]$ 表示到达点 $(i,j)$ 的路径数,状态转移方程为: $$dp[i][j] = \begin{cases} 0 & \text{若 } (i,j) \text{ 是障碍点} \\ dp[i-1][j] + dp[i][j-1] & \text{其他情况} \end{cases}$$ 边界条件:$dp[0][0] = 1$(起点非障碍时)[^3]。 2. **障碍标记** 预先标记所有被马控制的 $9$ 个点。 3. **遍历方向** 按行或列顺序遍历,保证计算 $dp[i][j]$ 时 $dp[i-1][j]$ 和 $dp[i][j-1]$ 已计算[^3]。 --- #### Java 代码实现 ```java import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int n = scanner.nextInt(); // 终点行 int m = scanner.nextInt(); // 终点列 int x = scanner.nextInt(); // 马的行 int y = scanner.nextInt(); // 马的列 // 标记障碍点(包括马的位置及其控制点) boolean[][] obstacle = new boolean[n + 1][m + 1]; int[][] dirs = {{0, 0}, {1, 2}, {2, 1}, {-1, 2}, {-2, 1}, {1, -2}, {2, -1}, {-1, -2}, {-2, -1}}; for (int[] d : dirs) { int nx = x + d[0]; int ny = y + d[1]; if (nx >= 0 && nx <= n && ny >= 0 && ny <= m) { obstacle[nx][ny] = true; } } // DP 初始化 long[][] dp = new long[n + 1][m + 1]; dp[0][0] = obstacle[0][0] ? 0 : 1; // 填充 DP 表 for (int i = 0; i <= n; i++) { for (int j = 0; j <= m; j++) { if (obstacle[i][j]) continue; if (i > 0) dp[i][j] += dp[i - 1][j]; if (j > 0) dp[i][j] += dp[i][j - 1]; } } System.out.println(dp[n][m]); } } ``` --- #### 关键点 1. **数组越界处理** 在标记马的攻击范围时,需检查坐标是否在棋盘范围内。 2. **数据类型选择** 使用 `long` 避免路径数过大导致溢出。 3. **边界条件** 若起点或终点本身是障碍点,直接输出 $0$[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值