洛谷 P1002 [NOIP2002 普及组] 过河卒 题解 (C语言递归解法)

题目描述:

核心思想:递归

A点到B点  采用每次一步一步的走

A——>B的路径条数=   C——>B  +   D——>B 

故可以看出  A到B的路径条数 等于   A左边的一格 及 下边的一格 到B的路径之和 

故递归思想就出来了    

C——>B = D到B + E到B

D——>B=E到B + F到B

…………

函数代码:

//旧版 P1002 [NOIP2002 普及组] 过河卒
#include<stdio.h>
#include<stdlib.h>
int x = 0, y = 0;//马的位置
int main()
{
	int n = 0, m = 0;//终点位置

	int i = 0, j = 0;//此时卒的位置
	int long long lujin(int n, int m, int i, int j);//申明
	scanf("%d %d %d %d", &n, &m, &x, &y);
	if (((x == 0) & (y == 0)) || ((x == n) && (y == m)) || (((abs(n - x) + abs(m - y)) == 3) && ((n != x) && (abs(m - y) != 3)) && ((m != y) && (abs(n - x) != 3))))
		printf("0");
	else
	{
		int long long zu = lujin(n, m, i, j);
		printf("%lld", zu);
	}




	return 0;
}

int long long lujin(int n, int m, int i, int j)
{


	if ((i + 1 == n) && (j == m))
		return 1;

	if ((i == n) && (j + 1 == m))
		return 1;

	if ((i + 1 <= n) && (j <= m) && (((abs(i + 1 - x) + abs(j - y)) != 3) || ((i + 1 == x) && (abs(j - y) == 3)) || ((j == y) && (abs(i + 1 - x) == 3))) && (((i + 1) != x) || (j != y)))// 判断x方向的这一步有否有效                                          马控制的全范围,(n,m)表格也没事 反正卒走不到那些位置
	{

		if ((i <= n) && (j + 1 <= m) && (((abs(i - x) + abs(j + 1 - y)) != 3) || ((i == x) && (abs(j + 1 - y) == 3)) || ((j + 1 == y) && (abs(i - x) == 3))) && ((i != x) || ((j + 1) != y)))//判断y方向这一步是否有效


			return (lujin(n, m, i + 1, j) + lujin(n, m, i, j + 1));   //都有的话 有两种路劲

		else

			return(lujin(n, m, i + 1, j));   //就x方向这一步有效


	}

	else if ((i <= n) && (j + 1 <= m) && (((abs(i - x) + abs(j + 1 - y)) != 3) || ((i == x) && (abs(j + 1 - y) == 3)) || ((j + 1 == y) && (abs(i - x) == 3))) && ((i != x) || ((j + 1) != y)))//判断y方向这一步是否有效


		return(lujin(n, m, i, j + 1));

	else
		return 0;//x y 方向都无效 }




}

代码解释:因为马的位置可为

所以马可能在 起点(0,0)位置 也可能在终点位置   所以这种情况就直接  输出0  表示没有路劲可达到。

这部分是 马可以跳 到的地方    就这张图来看  它能到达的位置是这个菱形 但是要抛去 红色四个点         关系均为

其中 函数abs()是绝对值函数

这部分及排除了 红色的点部分。

函数部分:

因为返回值可能比较大 故采用 long long类型   传递四个参数

递归参数为 6个参数也可以 带上X Y马的位置   

运行截图:

该算法有个缺陷: n m取比较大时 跑不出来   例如n=m=20

原因分析:是递归次数的指数型增长 

这里的二次的指数型增长 以及重复递归的结果

(0,0)递归到(0,1)和(1,0)

(0,1)递归到(0,2)和(1,1)

(1,0)递归到(1,2)和(1,1)

依次类推会 2的指数型增长  许多重复性递归

所以在答题系统里跑不过去   只能通过三个测试

优化思路:就是避免它的重复递归

比如这里(1,1)到终点的路径次数是固定不变的,不用每次都计算,而把第一次计算的值存下来,后面遇到了直接拿来使用!!!

原码如下:

//新版P1002 [NOIP2002 普及组] 过河卒
#include<stdio.h>
#include<stdlib.h>
int x = 0, y = 0;//马的位置

int long long arr[21][21] = { 0 };
int main()
{
	int n=0, m = 0;//终点位置
	
	int i = 0, j = 0;//此时卒的位置
	int long long lujin(int n, int m,int i, int j);//申明
	scanf("%d %d %d %d", &n,&m,&x,&y);
	if (((x == 0) & (y == 0)) || ((x == n) && (y == m)) || (((abs(n - x) + abs(m - y)) == 3) && ((n != x) && (abs(m - y) != 3)) && ((m != y) && (abs(n - x) != 3))))
		printf("0");
	else
	{
		int long long zu=lujin(n, m, i, j);
		printf("%lld", zu);
	}
	return 0;
}

int long long lujin(int n, int m, int i, int j)
{
	
	
		if ((i + 1 == n) && (j == m))
			return 1;

		if ((i == n) && (j + 1 == m))
			return 1;

		if ((i + 1 <= n) && (j <= m) && (((abs(i + 1 - x) + abs(j - y)) != 3) || ((i + 1 == x) && (abs(j - y) == 3)) || ((j == y) && (abs(i + 1 - x) == 3))) && (((i + 1) != x) || (j != y)))// 判断x方向的这一步有否有效                                          马控制的全范围,(n,m)表格也没事 反正卒走不到那些位置
		{

			if ((i <= n) && (j + 1 <= m) && (((abs(i - x) + abs(j + 1 - y)) != 3) || ((i == x) && (abs(j + 1 - y) == 3)) || ((j + 1 == y) && (abs(i - x) == 3))) && ((i != x) || ((j + 1) != y)))//判断y方向这一步是否有效

			{


				if (arr[i + 1][j] == 0)
				{
					arr[i + 1][j] = lujin(n, m, i + 1, j);
					if (arr[i][j + 1] == 0)
					{
						arr[i][j + 1] = lujin(n, m, i, j + 1);
						return(arr[i][j + 1] + arr[i + 1][j]);
					}
					else
						return(arr[i][j + 1] + arr[i + 1][j]);
				}
				else
				{
					if (arr[i][j + 1] == 0)
					{
						arr[i][j + 1] = lujin(n, m, i, j + 1);
						return(arr[i][j + 1] + arr[i + 1][j]);
					}
					else
						return(arr[i][j + 1] + arr[i + 1][j]);

				}

					//return (lujin(n, m, i + 1, j) + lujin(n, m, i, j + 1)); //都有的话 有两种路劲


			}
			else
			{
				if (arr[i + 1][j] == 0)
				{
					arr[i + 1][j] = lujin(n, m, i + 1, j);
					return arr[i + 1][j];
				}
				else
					return arr[i + 1][j];



				//return(lujin(n, m, i + 1, j));   //就x方向这一步有效
			}

		}

		else if ((i <= n) && (j + 1 <= m) && (((abs(i - x) + abs(j + 1 - y)) != 3) || ((i == x) && (abs(j + 1 - y) == 3)) || ((j + 1 == y) && (abs(i - x) == 3))) && ((i != x) || ((j + 1) != y)))//判断y方向这一步是否有效

		{
			if (arr[i][j+1] == 0)
			{
				arr[i][j+1] = lujin(n, m, i, j+1);
				return arr[i][j+1];
			}
			else
				return arr[i][j+1];

			//return(lujin(n, m, i, j + 1));

		}
		else
			return 0;//x y 方向都无效 



	
}

加了一个二维数组 用来记录每一个坐标 是否递归过     使用全局变量是因为它要在函数中使用,减少递归参数 故放在全局变量

用该语句替换之前的语句。

例如此时 i=a  j=b

递归之前  先判断arr[a][b]处的值    如果为初试值0 则为该位置未递归  所以采用递归并赋值及该位置就存下了  (a,b)到终点的路劲次数   后面用到了直接 return arr[a][b]即可 大大减小递归次数

同理后面递归的地方  只要是递归   就先判断递归坐标这个地方 是否 递归过

运行演示:秒出结果   运行速度大大提高

测试也均通过

<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]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值