P3956 [NOIP2017 普及组] 棋盘

这篇博客讨论了一个棋盘问题,玩家需要从左上角移动到右下角,经过有颜色的格子并支付不同颜色变化的金币。可以花费额外金币临时改变无色格子的颜色,但有使用限制。问题求解最小金币花费路径,通过有权值的BFS算法解决。当无法到达目标时,输出-1。

题目描述
有一个m \times mm×m的棋盘,棋盘上每一个格子可能是红色、黄色或没有任何颜色的。你现在要从棋盘的最左上角走到棋盘的最右下角。

任何一个时刻,你所站在的位置必须是有颜色的(不能是无色的), 你只能向上、 下、左、 右四个方向前进。当你从一个格子走向另一个格子时,如果两个格子的颜色相同,那你不需要花费金币;如果不同,则你需要花费 11个金币。

另外, 你可以花费 22 个金币施展魔法让下一个无色格子暂时变为你指定的颜色。但这个魔法不能连续使用, 而且这个魔法的持续时间很短,也就是说,如果你使用了这个魔法,走到了这个暂时有颜色的格子上,你就不能继续使用魔法; 只有当你离开这个位置,走到一个本来就有颜色的格子上的时候,你才能继续使用这个魔法,而当你离开了这个位置(施展魔法使得变为有颜色的格子)时,这个格子恢复为无色。

现在你要从棋盘的最左上角,走到棋盘的最右下角,求花费的最少金币是多少?

输入格式
第一行包含两个正整数m, nm,n,以一个空格分开,分别代表棋盘的大小,棋盘上有颜色的格子的数量。

接下来的nn行,每行三个正整数x, y, cx,y,c, 分别表示坐标为(x,y)(x,y)的格子有颜色cc。

其中c=1c=1 代表黄色,c=0c=0 代表红色。 相邻两个数之间用一个空格隔开。 棋盘左上角的坐标为(1, 1)(1,1),右下角的坐标为( m, m)(m,m)。

棋盘上其余的格子都是无色。保证棋盘的左上角,也就是(1, 1)(1,1) 一定是有颜色的。

输出格式
一个整数,表示花费的金币的最小值,如果无法到达,输出-1−1。

输入输出样例
输入 #1
5 7
1 1 0
1 2 0
2 2 1
3 3 1
3 4 0
4 4 1
5 5 0
输出 #1
8
输入 #2
5 5
1 1 0
1 2 0
2 2 1
3 3 1
5 5 0
输出 #2
-1

有权值的BFS,迷宫

const int N = 1010;
const int INF = 0xc0c0c0c0;
int n, m;
int g[N][N];
struct po
{
   
   
    int x, y, co, dis;
};

bool operator <(const po &a,const po &b){
   
   
    return a.dis > b.dis;
}
int dx[]{
   
   0, 1, 0, -1} ,dy[
<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`
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值