[2002J4]过河卒

题目描述

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

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

image-20230330105911496

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

输入格式

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

输出格式

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

样例 #1

样例输入 #1

6 6 3 3

样例输出 #1

6

提示

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

#include <iostream>
#include <algorithm>
using namespace std;

long long f[30][30];
int dis[8][2]={{1,2},{-1,2},{1,-2},{-1,-2},{2,1},{-2,1},{2,-1},{-2,-1}};
int main()
{
	//输入
	int n,m,mx,my;
	cin>>n>>m>>mx>>my;
	//初始化边界
	f[0][0]=1;
	//初始化“马”
	f[mx][my]=-1;
	for(int i=0;i<8;i++)
	{
		int x=mx+dis[i][0],y=my+dis[i][1];
		if(x>=0&&x<=n&&y>=0&&y<=m)
			f[x][y]=-1;
	}
	//遍历赋值
	for(int i=0;i<=n;i++)
		for(int j=0;j<=m;j++)
			if(f[i][j]!=-1&&!(i==0&&j==0))
			{
				long long q=(i>0 ? max(0ll,f[i-1][j]) : 0);
				long long w=(j>0 ? max(0ll,f[i][j-1]) : 0);
				f[i][j]=q+w;
			}
	//输出
	if(f[n][m]==-1)
		cout<<0;
	else
		cout<<f[n][m];
	return 0;
}

这道题是DP,小坑特别多,交了8遍才过!主要问题有:base,优先级,long long,等等,等等……主要难点在“遍历赋值”区。

<think>嗯,用户问的是如何用Python解决过河问题,也就是NOIP 2002普及组的P1002题目。首先,我需要回忆一下这个问题的具体要求。题目大意是说,一个子要从起点A走到终点B,棋盘上的点有一些被对方的马控制住了,不能经过。需要计算所有可能的路径数目,且子只能向右或向下移动。 那首先得明确几个关键点。首先,棋盘的大小,应该是从(0,0)到(n,m),而马的位置是给定的,并且马能控制的点包括自身和周围的八个点,这些点都是不能走的。然后,路径的计算应该是一个动态规划的问题,因为每个点的路径数等于左边和上边点的路径数之和,前提是该点可走。 接下来,我得考虑如何用Python实现动态规划。通常,动态规划问题需要一个二维数组来存储每个位置的路径数。初始化的时候,起点(0,0)的路径数是1,如果起点不被马控制的话。然后,对于每个点(i,j),如果该点可走,那么dp[i][j] = dp[i-1][j] + dp[i][j-1]。需要考虑边界情况,比如第一行和第一列,这时候只能从左边或上边来,不能越界。 然后,关于马的控制的点,需要预先计算出来。例如,给定马的坐标(x,y),那么被控制的点包括周围的八个点以及自己,所以需要把这些点标记为不可达。在代码中,可以用一个二维数组来标记这些点,或者在计算每个点时检查是否在被控制的点集合里。 另外,需要注意数据范围,题目中可能有较大的数值,导致溢出。不过,在Python中整数的大小不用担心,但如果是其他语言可能需要用长整型。这里用户要求用Python,所以不用考虑这个问题。 然后,测试一下样例输入,比如题目中的样例输入是6,6,3,3,输出应该是6。这时候需要确保代码能正确处理这种情况。例如,马的位置在(3,3),那么被控制的点包括周围的八个点和自己,这些位置在棋盘上会被标记,不能经过。然后,路径计算的时候需要跳过这些点。 可能的错误点包括:初始化dp数组的时候没有正确处理被控制的起点,或者在动态规划的过程中没有正确跳过被控制的点。另外,边界情况的处理,比如当第一行或第一列的点被控制时,后面的路径数应该是0,因为无法到达。 接下来,看看用户提供的引用。引用3和4提到了使用二维数组dp[i][j],并且递推式是dp[i][j] = dp[i-1][j] + dp[i][j-1],这应该是正确的。引用5也提到需要计算路径数,假设马的位置固定。所以动态规划的方法是适用的。 现在,考虑代码的结构。首先输入终点B的坐标(n,m)和马的坐标(x,y)。然后,生成被马控制的点,存入一个集合。然后初始化dp数组,大小应该是(n+1) x (m+1),因为坐标从0开始。然后,遍历每个点,如果该点被控制,则dp[i][j] = 0。否则,如果是起点,则dp[0][0]=1。否则,计算左边和上边的和。 需要注意的是,在遍历的时候,如果i或j为0,即第一行或第一列,只能从左边或上边来。例如,当i=0时,只能从左边来,即j-1的位置。当j=0时,只能从i-1的位置来。需要处理这些情况,避免数组越界。 然后,编写代码时需要注意Python中的二维数组初始化。可以用一个列表的列表,初始化为全0,然后逐步填充。 另外,测试样例的情况,比如当起点被控制的话,直接输出0。或者当终点被控制的话,也是0。 现在,具体的代码实现步骤: 1. 读取输入,通常是n, m, x, y。假设输入是终点坐标(n, m),马的位置(x, y)。需要注意题目中的输入顺序,比如是否终点是B点的坐标,可能要看题目描述。例如,原题可能输入的是B点的坐标,即(n, m),而马的坐标是(x, y)。比如样例输入是6 6 3 3,对应的终点是(6,6),马在(3,3)。 2. 生成被控制的点。马的坐标是(x, y),被控制的点包括: (x, y) (x+1, y+2) (x+2, y+1) (x+2, y-1) (x+1, y-2) (x-1, y-2) (x-2, y-1) (x-2, y+1) (x-1, y+2) 总共九个点。但是需要注意这些点是否在棋盘范围内。例如,当x-2 <0或者y+2超过m的时候,这些点可能不存在。但是,在动态规划过程中,这些点会被自动排除,因为遍历的时候i和j的范围是0到n和0到m。不过,在生成被控制的点时,应该包括所有可能的点,不管是否在棋盘内,因为在动态规划的过程中,当判断一个点是否在棋盘内时,已经被处理了。或者,也可以在生成被控制点时,检查是否在棋盘范围内。但根据原题的意思,被控制的点即使超出棋盘范围,是否会影响?应该是不影响的,因为只有棋盘内的点才会被考虑。所以,生成所有可能的九个点,并存储到集合中。当遍历时,遇到这些点且在棋盘范围内的话,就标记为不可达。 或者,生成时可以直接检查是否在0<=x<=n和0<=y<=m的范围内,但可能没有必要,因为不在棋盘范围内的点不会被遍历到。例如,在遍历i和j的时候,范围是0到n和0到m,所以即使被控制的点超出这个范围,也不会被访问到。所以,在生成被控制点时,可以不管是否在棋盘内,然后在遍历的时候判断该点是否在棋盘内和被控制。 或者,可能更高效的是在生成被控制点的时候,排除那些超出棋盘范围的坐标。例如,马的坐标是(x, y),然后周围的八个点可能超出棋盘,所以需要检查它们的坐标是否在0<=x<=n和0<=y<=m之间。例如,原题中的棋盘范围是0到n和0到m? 可能原题中的棋盘是B点坐标为(n, m),所以棋盘的大小是(n+1) x (m+1),因为包括从0到n的n+1个点。例如,如果B是(6,6),那么棋盘是0到6,共7个点。这时候,被控制的点必须是在0到n和0到m之间的坐标才会被考虑。否则,即使被马控制,但位于棋盘之外,不影响路径。 例如,在样例输入中,马的位置是(3,3),周围的点可能包括(4,5),但n和m是6,所以该点在棋盘范围内。但例如,如果马的坐标是(0,0),那么周围的点可能包括(-1,2),这在棋盘外,不会被考虑。 因此,在生成被控制的点时,应该生成所有可能的九个点,然后过滤掉那些不在棋盘范围内的点。或者,在代码中直接生成所有可能的点,然后在动态规划过程中,当遍历到每个点时,检查是否在被控制点集合中。如果该点不在棋盘范围内,则不会被访问到,所以不需要处理。 比如,被控制的点可能包括(x-1, y-2),如果x-1 <0或者y-2 <0,那么这些点不会在动态规划的遍历中被处理,所以无需过滤。 所以,在生成被控制的点时,可以直接生成所有九个点,不管是否超出棋盘范围。因为在遍历的时候,i的范围是0到n,j是0到m,所以这些被控制的点如果在棋盘外的话,不会被遍历到,所以不影响。 接下来,初始化dp数组。创建一个二维数组,大小为(n+1) x (m+1),初始化为0。然后,起点dp[0][0] = 1,如果起点不在被控制的点集合中,否则为0。 然后,遍历每个点,按行或列的顺序。比如,双重循环,i从0到n,j从0到m。对于每个点(i,j),如果(i,j)在被控制的点集合中,则dp[i][j] =0。否则,如果i==0且 j==0,那么dp[0][0]已经被初始化。否则,计算左边和上边的值的和。如果i>0,则加上dp[i-1][j],如果j>0,则加上dp[i][j-1]。 需要注意的是,对于第一行(i=0),只能从左边(j-1)得到路径数。例如,当i=0且j>0时,dp[i][j] = dp[i][j-1]。同样,当j=0且i>0时,dp[i][j] = dp[i-1][j]。 同时,如果当前点(i,j)被控制的话,dp[i][j]设为0,这样后续的点无法从这里经过。 举个例子,假设起点(0,0)不被控制,那么dp[0][0]=1。然后,对于第一行的点(0,1),如果该点不被控制,那么dp[0][1]=dp[0][0] =1。如果被控制的话,则为0,后续的点无法从这经过。 这样,整个动态规划的过程就能正确计算路径数目。 现在,将这些思路转化为Python代码。首先,读取输入。例如,输入可能是四个整数,n, m, x, y。例如,样例输入是6 6 3 3。然后,生成被控制的点集合。 代码的大致步骤: 1. 读取输入n, m, x, y。 2. 生成被控制的点集合。 3. 初始化dp二维数组。 4. 遍历每个点,计算dp[i][j]。 5. 输出dp[n][m]。 接下来,具体实现生成被控制点的部分: 被控制的点包括: (x, y), (x+1, y+2), (x+2, y+1), (x+2, y-1), (x+1, y-2), (x-1, y-2), (x-2, y-1), (x-2, y+1), (x-1, y+2) 将这些点存储在一个集合中,例如: control_points = set() 然后,将这些点逐一添加到集合中。 例如: dx = [0, 1, 2, 2, 1, -1, -2, -2, -1] dy = [0, 2, 1, -1, -2, -2, -1, 1, 2] for k in range(9): nx = x + dx[k] ny = y + dy[k] control_points.add( (nx, ny) ) 这样,生成九个点。 然后,初始化dp数组: n和m是终点坐标。假设输入的n和m是棋盘的终点坐标,比如棋盘是0到n的n+1行,0到m的m+1列。 所以,dp数组的大小为(n+1) x (m+1)。可以用列表推导式初始化: dp = [[0]*(m+1) for _ in range(n+1)] 然后,处理起点: if (0, 0) not in control_points: dp[0][0] = 1 else: dp[0][0] =0 然后,遍历每个点i和j: for i in range(n+1): for j in range(m+1): if i ==0 and j ==0: continue # 已经处理过 if (i,j) in control_points: dp[i][j] =0 else: if i >0: dp[i][j] += dp[i-1][j] if j >0: dp[i][j] += dp[i][j-1] 但是,这样的双重循环可能会覆盖一些情况。例如,当i和j都为0的时候已经被处理过。或者,可以合并到循环内部: for i in range(n+1): for j in range(m+1): if (i, j) in control_points: dp[i][j] =0 else: if i ==0 and j ==0: dp[i][j] =1 else: sum_val =0 if i >0: sum_val += dp[i-1][j] if j >0: sum_val += dp[i][j-1] dp[i][j] = sum_val 这样,无论i和j是否为0,都会被处理。不过,这样可能在每次循环中都要判断是否是被控制的点,导致效率问题。但对于题目中的数据范围,这应该不是问题。 现在,测试样例输入: 输入:6 6 3 3 被控制的点包括: (3,3)周围的八个点,如(4,5), (5,4), (5,2), (4,1), (2,1), (1,2), (1,4), (2,5),以及(3,3)自己。 然后,动态规划计算路径数时,这些点被设为0。例如,当i=3,j=3时,dp[3][3]=0,因为被控制。 那么,路径数最终是6。代码是否能正确输出? 可能需要进一步测试代码是否正确。 此外,需要注意坐标的方向。比如,在动态规划中,是否i代表行,j代表列?是的,因为棋盘应该是二维的,所以i和j的遍历是正确的。 另外,原题中的只能向下或向右走,也就是每一步只能向右或向下移动,所以动态规划的递推式是正确的。 现在,编写代码: n, m, x, y = map(int, input().split()) control_points = set() dx = [0, 1, 2, 2, 1, -1, -2, -2, -1] dy = [0, 2, 1, -1, -2, -2, -1, 1, 2] for k in range(9): nx = x + dx[k] ny = y + dy[k] control_points.add( (nx, ny) ) dp = [[0]*(m+1) for _ in range(n+1)] for i in range(n+1): for j in range(m+1): if (i, j) in control_points: dp[i][j] =0 else: if i ==0 and j ==0: dp[i][j] =1 else: sum_val =0 if i >0: sum_val += dp[i-1][j] if j >0: sum_val += dp[i][j-1] dp[i][j] = sum_val print(dp[n][m]) 现在,测试样例输入: 输入: 6 6 3 3 预期输出6。运行代码是否能得到正确结果? 需要手动模拟一下。例如,被控制的点包括: 马的位置是(3,3),所以生成的点是: (3,3) (4,5) (5,4) (5,2) (4,1) (2,1) (1,2) (1,4) (2,5) 现在,棋盘是0到6行,0到6列。 计算每个点的路径数。 例如,当i=0,j=0到6: 除了被控制的点外,其他点路径数累积。例如,如果起点(0,0)不在控制点,则初始化为1。然后,j=1到6: 对于每个j,如果(0,j)不在控制点,则dp[0][j] = dp[0][j-1],否则0。 同样,i=1到6,j=0到6。 可能,当遍历到某个被控制的点时,该点的dp值设为0,从而后面的点无法从这里经过。 这样,最终在(6,6)的位置得到6。 因此,这个代码应该可以正确处理样例输入。 现在,用户提供的引用中有提到如何用Python解决这个问题。例如,引用3和4讨论了动态规划的方法,这与我们的思路一致。所以,可以确认该方法是正确的。 另外,需要处理一些特殊情况,比如当起点或终点被控制时,直接输出0。例如,如果输入的n=0,m=0,则只有起点,如果被控制则输出0,否则1。 此外,还需要考虑马的位置是否可能在棋盘之外?根据题目描述,马的位置是输入的,但题目可能保证其在棋盘范围内。所以,代码可能不需要处理这种情况。 综上所述,代码应该可以正确解决问题。现在,将代码和解释整合到回答中,并生成相关问题。</think>### Python实现NOIP 2002普及组过河问题解法 #### 问题分析 过河问题要求计算从棋盘左上角$(0,0)$到右下角$(n,m)$的路径总数,其中马控制的点(包含自身及周围8个位置)不可通行,且只能向右或向下移动[^5]。核心思路是使用动态规划(DP),状态转移方程为: $$dp[i][j] = dp[i-1][j] + dp[i][j-1]$$ 其中$dp[i][j]$表示到达点$(i,j)$的路径数[^4]。 #### 代码实现 ```python n, m, x, y = map(int, input().split()) # 生成马控制的坐标点 control_points = set() dx = [0, 1, 2, 2, 1, -1, -2, -2, -1] dy = [0, 2, 1, -1, -2, -2, -1, 1, 2] for k in range(9): nx = x + dx[k] ny = y + dy[k] control_points.add((nx, ny)) # 初始化DP数组 dp = [[0] * (m + 1) for _ in range(n + 1)] dp[0][0] = 1 if (0, 0) not in control_points else 0 # 动态规划计算路径数 for i in range(n + 1): for j in range(m + 1): if (i, j) in control_points: dp[i][j] = 0 continue if i == 0 and j == 0: continue sum_val = 0 if i > 0: sum_val += dp[i - 1][j] if j > 0: sum_val += dp[i][j - 1] dp[i][j] = sum_val print(dp[n][m]) ``` #### 实现说明 1. **控制点标记**:通过马的坐标生成所有受控点,使用集合存储确保快速查找[^3]。 2. **边界处理**:第一行和第一列单独处理,避免数组越界。 3. **状态转移**:每个点的路径数由左侧和上方的路径数之和决定,受控点直接置0。 #### 测试示例 输入:`6 6 3 3` 输出:`6`
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值