洛谷P1002(过河卒 [NOIP 2002 普及组])题解

题目大意:棋盘上有一个卒,要从A点(0,0)走到B点(n,m),只能向右或向下走。但是棋盘上有一个马的位置,马所在的位置和它能一步跳到的点都是被控制的,卒不能经过这些点。我要计算有多少条路径可以到达B点。

首先,我们需要明确问题的关键点。首先是马的控点,也就是卒不能走的点。其次是路径的统计,这应该是一个动态规划的问题,因为每个点的路径数可能由其左边和上方的点决定,但必须排除被马控制的点。

那首先我们得确定哪些点是马的控制点。马的坐标是给定的,比如马的坐标是(x,y),那么根据马的走法,周围会有八个可能的点会被控制,包括马自己的位置。比如,假设马在(3,3),那么它周围的点可能有(4,5)、(5,4)、(2,1)等等。需要把这些点都标记为不可达的点。

然后,动态规划的思路应该是建立一个二维数组dp,其中dp[i][j]表示从起点到(i,j)点的路径数目。初始化的时候,起点dp[0][0]应该是1,如果起点没有被马控制的话。然后,对于每个点(i,j),如果该点不是被控制的点,那么dp[i][j] = dp[i-1][j] + dp[i][j-1],也就是从左边和上边的点过来。但是如果左边或上边的点被控制,那么相应的路径数就不能加上。

不过,这里需要注意边界情况。比如,当i=0的时候,只能从左边来,也就是j轴的方向。同样,当j=0的时候,只能从上方来,也就是i轴的方向。但是如果起点被马控制的话,整个路径数就是0,因为无法移动。

接下来,我需要考虑如何标记马的控制点。首先,输入给出的是B点的坐标(n,m)和马的坐标(x,y)。比如样例输入是6 6 3 3,也就是B点是(6,6),马在(3,3)。那么马的控制点包括:

- 马自己的位置(3,3)

- 周围的八个点:(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)

这些坐标需要检查是否在棋盘范围内(比如,不能是负数,或者超过n或m的范围)。比如,假设马的坐标是(0,0),那么某些方向可能无法存在,所以需要排除这些点。

然后,遍历所有可能的点,如果点(i,j)是马的控制点,则标记该点为障碍,不能走。

接下来,动态规划的初始化。对于第一行(i=0),如果路径上的某个点被马控制,那么该点及之后的所有点的路径数都为0,因为无法到达。同样的,对于第一列(j=0),也是类似的情况。

比如,在样例输入中,B点是(6,6),所以棋盘的范围是0<=i<=6,0<=j<=6。马的位置是(3,3),控制点包括:

(3,3)

(4,5), (5,4), (5,2), (4,1), (2,1), (1,2), (1,4), (2,5)

需要检查这些点是否在棋盘范围内。比如,对于n=6,m=6来说,最大的坐标是6。所以,例如(4,5)是有效的,而(5,4)也在范围内。比如,假设马的位置是(3,3),那么上述点中的某些可能超出范围吗?比如(4,5)中的x=4<=6,y=5<=6,没问题。而比如(5,4)也有效。其他的点可能比如(3-2,3+1)= (1,4)也有效。所以所有这些点都需要被标记为障碍。

在样例中,路径数为6。那可能意味着,在这些障碍点的情况下,存在6条不同的路径从起点到终点。

那动态规划的递推式应该是,如果当前点不是障碍,那么dp[i][j] = dp[i-1][j] + dp[i][j-1]。如果是障碍,则dp[i][j]=0。

那么,具体的步骤应该是:

1. 输入B点的坐标n, m,以及马的坐标x, y。

注意,题目中的输入是四个正整数,分别表示B点坐标和马的坐标。比如样例输入是6 6 3 3,那么B点是(6,6),马的位置是(3,3)。但根据坐标系的表示,可能需要确认是否起点是(0,0),终点是(n,m)。比如,如果输入是n=6,m=6的话,那么棋盘是0到6的坐标,总共有7行7列?或者是否坐标是1-based?比如题目中的描述可能需要明确坐标的范围。例如,原题中的描述可能应该确认坐标的范围,比如是否允许n和m为0。但根据样例输入,输入是6 6,而马的坐标是3 3,那么可能坐标是0-based的,所以B点坐标是(n,m)等于(6,6)的话,总共有7x7的格子?

需要明确题目中的棋盘坐标是否是0-based,比如起点是(0,0),终点是(n,m)。例如,当n=6,m=6时,棋盘的行数是n+1,列数是m+1。比如,总共有7x7个点。这个时候,当计算路径的时候,每个点只能向右或向下移动一步。

那这时候,动态规划的数组的大小应该是(n+1)行,(m+1)列。比如,当n=6时,i的范围是0到6,共7个点。

接下来,如何标记马的控制点:

马的位置是(x,y),那么其周围的八个可能的点需要计算。例如,马走日的八个方向:

(x+2, y+1)

(x+2, y-1)

(x-2, y+1)

(x-2, y-1)

(x+1, y+2)

(x+1, y-2)

(x-1, y+2)

(x-1, y-2)

再加上马自身的位置(x,y),都是被控制的点。对于每个可能的这些坐标,需要检查是否在棋盘范围内,即是否0<=X<=n,0<=Y<=m。如果超出范围的话,就不需要标记。

例如,假设马的位置是(3,3),n=6,m=6。那么上述所有方向上的坐标是否在范围内?

比如,(3+2,3+1)=(5,4) <=6,有效。同样,(3+1,3+2)=(4,5)也有效。而其他方向比如(3-2,3+1)=(1,4),也有效。所以所有八个点都可能有效。

将这些点都标记为障碍点。

接下来,初始化dp数组。dp[0][0]的值取决于起点是否被马控制。如果起点是障碍点的话,那么无法移动,结果为0。否则,dp[0][0]=1。

然后,处理第一行和第一列:

对于第一行(i=0),每个点只能从左边过来。所以,如果当前位置不是障碍,且左边的点可达,那么当前点的路径数等于左边点的路径数。如果左边点不可达,那么当前点也不可达。

例如,假设i=0,j从0到m。对于每个j,如果j=0时已经处理。当j>=1时,如果当前点不是障碍,并且dp[0][j-1] >0,那么dp[0][j] = dp[0][j-1]。否则,dp[0][j]=0。

同样的,处理第一列(j=0,i>=1):如果当前点不是障碍,并且dp[i-1][0] >0,则dp[i][0] = dp[i-1][0]。否则,0。

然后,对于其他点i>0且j>0的情况,如果当前点不是障碍,那么dp[i][j] = dp[i-1][j] + dp[i][j-1]。如果是障碍的话,dp[i][j]=0。

这样遍历整个棋盘,最终dp[n][m]即为答案。

现在,需要考虑如何实现这个动态规划过程。

首先,创建一个二维数组dp,大小为(n+1)x(m+1)。同时,创建一个二维数组obstacle,同样大小,用来标记是否是障碍点。

然后,初始化障碍数组。遍历马的位置及其八个可能的方向,将范围内的点标记为障碍。

然后,初始化dp数组。首先检查起点是否被标记为障碍,如果是的话直接返回0。

否则,初始化dp[0][0]=1。然后处理第一行和第一列。

然后,遍历其他点。

需要注意,当i=0或者j=0时,处理方式不同。

现在,举个例子,比如样例输入:

输入:6 6 3 3。也就是终点是(6,6),马的位置是(3,3)。

需要标记的障碍点包括:

(3,3)

以及八个方向:

(3+2, 3+1) → (5,4)

(3+2, 3-1) → (5,2)

(3-2,3+1) → (1,4)

(3-2,3-1) → (1,2)

(3+1,3+2) → (4,5)

(3+1,3-2) → (4,1)

(3-1,3+2) → (2,5)

(3-1,3-2) → (2,1)

然后,这些点是否都在0<=x<=6,0<=y<=6范围内?是的,所以共有9个障碍点。

然后,我们需要创建7x7的棋盘,检查每个点是否是障碍。

初始化dp数组。dp[0][0]是否为障碍?如果起点(0,0)不在障碍点中,那么dp[0][0]=1。否则为0。例如,在样例输入中,起点是(0,0),而障碍点是马的周围,所以不会被标记,所以dp[0][0]=1。

处理第一行i=0,j从1到6:

对于每个j,检查当前点是否是障碍。例如,j=0的时候已经处理过了。假设在i=0时,比如在样例输入中,是否有障碍点?比如,障碍点中有没有在第一行(i=0)的点?比如,比如马的某个方向可能导致障碍点在第一行?

比如,假设马的坐标是(0,0),那么障碍点可能包括(0,0), (2,1), (1,2)等等。如果在样例输入中,假设i=0的某个点被标记为障碍,那么后面的点就不能到达。

假设在样例输入中,起点不是障碍,那么处理第一行:

i=0,j=0 → dp[0][0]=1

j=1时,点(0,1)是否被障碍?要看是否在马的控点列表中。假设在样例中,马的控点中没有这个点,所以dp[0][1] = dp[0][0] =1。同样,后面的点如果未被阻挡,都继承左边的值。

同理处理第一列j=0,i从1到6。

然后,其他点按照dp[i][j] = 上边加左边的方式计算。

在样例中,最终的路径数目是6。这说明在存在障碍的情况下,共有6条路径。

现在,需要考虑如何处理障碍点的问题。比如,如何遍历马的八个方向?

可以用一个数组存储八个方向的偏移量。例如:

int dirs[8][2] = {{2,1}, {2,-1}, {-2,1}, {-2,-1}, {1,2}, {1,-2}, {-1,2}, {-1,-2}};

然后,对于马的坐标(x,y),遍历这八个方向,生成可能的点,并判断是否在棋盘范围内。如果在的话,标记为障碍。

此外,马的自身位置(x,y)也要标记为障碍。

这样,可以确保所有被控制的点都被正确标记。

现在,关于动态规划的初始化,当处理第一行的时候,如果某个点被障碍阻挡,那么后面的点都无法到达。比如,第一行i=0,j=3的位置被阻挡,那么j>=3的点都无法到达。

同样的,对于第一列也是如此。

在代码实现中,需要注意数组的索引是否越界。例如,当n和m的值较大时,但题目给出的数据范围是n,m<=20,所以数组的大小最多是21x21,这在内存上是可行的。

另外,由于结果可能很大,比如当n=20,m=20的时候,路径数目可能超过int的范围。例如,假设没有障碍的话,路径数目是组合数C(40,20)=137846528820,这会超过int的范围,因此需要用long long类型来存储dp数组的值。

但是在样例输入中,输出是6,显然在int范围内。但题目中的测试数据是否有可能超过int的范围?所以,应该使用long long类型来避免溢出。

所以,在代码中,dp数组的类型应该是long long。

现在,总结解题步骤:

1. 读取输入n, m, x, y。其中,B点坐标是(n,m),马的位置是(x,y)。

2. 检查坐标是否有效。比如,马的坐标是否在0<=x<=n,0<=y<=m。但题目中给出输入是有效的,所以可能不需要处理。

3. 创建障碍数组,标记所有马的控制点。

4. 创建动态规划数组dp,初始化为0。处理起点,如果起点不是障碍,则dp[0][0]=1。

5. 处理第一行和第一列。

6. 处理其他点,根据动态规划方程计算每个点的路径数目。

7. 输出dp[n][m]的值。

需要注意的问题:

- 数组越界问题:在标记马的控点时,必须确保生成的坐标在0<=i<=n,0<=j<=m范围内。

- 数据类型溢出:必须使用足够大的数据类型,如long long。

- 动态规划的顺序:必须按行或列的顺序处理,确保每个点的左边和上方的点已经被处理过。

现在,编写代码的大纲:

首先,读取输入:

int n, m, x, y;

cin >> n >> m >> x >> y;

然后,创建棋盘的范围是0到n,0到m。

创建一个二维数组obstacle,大小是(n+1)行,(m+1)列。初始化为false。

然后,标记障碍点:

obstacle[x][y] = true;

然后,遍历八个方向:

for 每个方向 in dirs:

int nx = x + dx;

int ny = y + dy;

if nx >=0 && nx <=n && ny >=0 && ny <=m:

obstacle[nx][ny] = true;

然后,初始化dp数组:

long long dp[n+1][m+1];

memset(dp, 0, sizeof(dp));

如果起点(0,0)不是障碍:

if (!obstacle[0][0]) dp[0][0] = 1;

否则,输出0。

处理第一行:

for (int j=1; j<=m; j++) {

if (!obstacle[0][j] && dp[0][j-1] >0) {

dp[0][j] = dp[0][j-1];

} else {

dp[0][j] = 0;

}

}

处理第一列:

for (int i=1; i<=n; i++) {

if (!obstacle[i][0] && dp[i-1][0] >0) {

dp[i][0] = dp[i-1][0];

} else {

dp[i][0] = 0;

}

}

处理其他点:

for (int i=1; i<=n; i++) {

for (int j=1; j<=m; j++) {

if (!obstacle[i][j]) {

dp[i][j] = dp[i-1][j] + dp[i][j-1];

} else {

dp[i][j] = 0;

}

}

}

最后输出dp[n][m]。

需要注意的是,当n=0或m=0时,比如当B点就在起点的情况下,比如n=0,m=0。这时如果起点不是障碍,则路径数目是1,否则是0。

另外,在C++中,变长数组(如long long dp[n+1][m+1])可能在编译器支持的情况下工作,但更安全的方式是使用动态分配的数组,或者使用vector。

但是,由于n和m的范围较小(<=20),可以安全地使用二维数组。

例如,在代码中:

const int MAX = 21;

long long dp[MAX][MAX];

bool obstacle[MAX][MAX] = {false};

这样,数组的大小足够容纳最大情况。

所以,正确的代码可能如下:

#include <iostream>

using namespace std;

int main() {

int n, m, x, y;

cin >> n >> m >> x >> y;

const int dirs[8][2] = {{1,2}, {1,-2}, {-1,2}, {-1,-2}, {2,1}, {2,-1}, {-2,1}, {-2,-1}};

bool obstacle[21][21] = {false};

// 标记马的控点

obstacle[x][y] = true;

for (int i=0; i<8; i++) {

int nx = x + dirs[i][0];

int ny = y + dirs[i][1];

if (nx >=0 && nx <=n && ny >=0 && ny <=m) {

obstacle[nx][ny] = true;

}

}

// 初始化dp数组

long long dp[21][21] = {0};

if (!obstacle[0][0]) {

dp[0][0] = 1;

}

// 处理第一行

for (int j=1; j<=m; j++) {

if (!obstacle[0][j] && dp[0][j-1] >0) {

dp[0][j] = dp[0][j-1];

}

}

// 处理第一列

for (int i=1; i<=n; i++) {

if (!obstacle[i][0] && dp[i-1][0] >0) {

dp[i][0] = dp[i-1][0];

}

}

// 处理其他点

for (int i=1; i<=n; i++) {

for (int j=1; j<=m; j++) {

if (!obstacle[i][j]) {

dp[i][j] = dp[i-1][j] + dp[i][j-1];

}

}

}

cout << dp[n][m] << endl;

return 0;

}

但是,这里有一个问题:在处理第一行和第一列的时候,代码中的条件是否正确?

例如,处理第一行的时候,每个点是否被障碍阻挡。例如,当处理j=1时,如果当前点被阻挡,那么即使左边的点可达,当前点也不可达,所以应该设置dp[0][j]=0。代码中的条件是正确的,只有当当前点不是障碍且左边可达时,才能设置dp[0][j]的值。否则,dp[0][j]保持0,因为数组被初始化为0。

同样的,处理第一列的时候,只有当当前点不是障碍且上方的点可达时,才能设置dp[i][0]的值。

这样,代码应该是正确的。

现在,测试样例输入6 6 3 3,输出应该是6。

例如,在代码中,当处理到各个点的时候,障碍点被正确标记,路径数被正确计算。

但是,在样例中,为什么输出是6?假设在动态规划过程中,某些路径被障碍阻挡,导致最终只有6条路径。

因此,上述代码应该能正确处理这种情况。

另一个测试情况是,如果起点被马控制,那么输出0。例如,输入0 0 0 0,此时马的控点包括起点,所以输出0。

另外,如果马的控点位于某条必经之路,那么路径数可能为0。例如,当B点是(1,1),马的位置是(0,1),那么控点包括(0,1)、(2,2)(但可能超出范围),其他控点可能包括其他位置。此时,起点是(0,0),必须走到(0,1)或(1,0)。如果(0,1)是障碍,则只能向下走。比如,如果(1,0)未被阻挡,那么路径数目是1。如果(1,0)也被阻挡,那么路径数目是0。

综上,上述代码应该能正确处理所有情况。

再整理后,可得出代码。

AC代码:

#include <iostream>
using namespace std;

int main() {
    int n, m, x, y;
    cin >> n >> m >> x >> y;

    const int dirs[8][2] = {{1, 2}, {1, -2}, {-1, 2}, {-1, -2},
                               {2, 1}, {2, -1}, {-2, 1}, {-2, -1}};
    bool obstacle[21][21] = {false};
    obstacle[x][y] = true;
    for (int i = 0; i < 8; ++i) {
        int nx = x + dirs[i][0];
        int ny = y + dirs[i][1];
        if (nx >= 0 && nx <= n && ny >= 0 && ny <= m) {
            obstacle[nx][ny] = true;
        }
    }

    long long dp[21][21] = {0};
    if (!obstacle[0][0]) {
        dp[0][0] = 1;
    }

    for (int j = 1; j <= m; ++j) {
        if (!obstacle[0][j]) {
            dp[0][j] = dp[0][j-1];
        }
    }

    for (int i = 1; i <= n; ++i) {
        if (!obstacle[i][0]) {
            dp[i][0] = dp[i-1][0];
        }
    }

    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            if (!obstacle[i][j]) {
                dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
    }

    cout << dp[n][m] << endl;
    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、付费专栏及课程。

余额充值