马拦过河卒(递推)

马踏过河卒 SDUTOJ1265

  • 问题:棋盘上A点有一个过河卒,需要走到目标B点。

 

  • 卒行走的规则:可以向下、或者向右。 同时在棋盘上的任一点有一个对方的马(如下图中的C点),该马所在的点和所有跳跃一步可达的点称为对方马的控制点(如下图中的C点和P1,P2,……,P8)。

 

  • 卒不能通过对方马的控制点。

 

  • 棋盘用坐标表示,A点(0,0)、B点(n, m) (n,m为不超过20的整数),同样马的位置坐标是需要给出的,C≠A且C≠B。

 

  • 编程:从键盘输入n,m,计算出卒从A点能够到达B点的路径的条数。

 

 

马踏过河卒是一道很老的题目,一些程序设计比赛中也经常出现过这一问题的变形。

 

一看到这种类型的题目容易让人想到用搜索来解决,但盲目的搜索仅当n,m=15就会超时。

 

可以试着用递推来进行求解。

 

根据卒行走的规则,过河卒要到达棋盘上的一个点,只能有两种可能:从左边过来(左点)或是从上面过来(上点)

根据加法原理,过河卒到达某一点的路径数目,就等于其到达其相邻的上点和左点的路径数目之和,因此可用逐列(或逐行)递推的方法求出从起点到终点的路径数目。障碍点(马的控制点)也完全适用,只要将到达该点的路径数目设置为0即可。

 

分析:

用二维数组元素f[i][j]表示到达点(i,j)的路径数目。

用g[i][j]表示点(i,j)是否是对方马的控制点。  

g[i][j]=0 非马控制点               g[i][j]=1 马控制点

可以得到如下的递推关系式:

(1)f[i][j] = 0 当g[i][j]=1,马控制点(i,j)路径数为0

(2)f[i][0] = f[i-1][0] 当i>0, g[i][j]=0 非马控制点,首列下点(i,0)的路径数为其上点(i-1,0)的路径数

(3)f[0][j] = f[0][j-1] 当j>0, g[i][j]=0 非马控制点,首行右点(0,j)的路径数为其左点(0,j-1)的路径数

(4)f[i][j] = f[i-1][j] + f[i][j-1]  当i>0, j>0, g[i][j]=0   非马控制点,(i,j)的路径数为其上点(i-1,j)h和左点(i,j-1)的路径数                  

递推边界:f[0][0] = 1

 

 

#include //马踏过河卒
int main()
{
    int i, j, n, m, x, y;
    //初始化所有点步数f为0
    //初始化所有点g为非马控制点0
    int f[20][20] = {0}, g[20][20] = {0};
    scanf("%d%d%d%d",
          &n, &m, &x, &y);
    f[0][0] = 1; //边界条件
    g[x][y] = 1;  //初始化马所在点C为1
    //初始化马的8个控制点为1
    //P6,P7,P5,P8,P4,P1,P3,P2
    g[x - 1][y - 2] = 1;
    g[x + 1][y - 2] = 1;
    g[x - 2][y - 1] = 1;
    g[x + 2][y - 1] = 1;
    g[x - 2][y + 1] = 1;
    g[x + 2][y + 1] = 1;
    g[x - 1][y + 2] = 1;
    g[x + 1][y + 2] = 1;
    for (i = 1; i <= n; i++)  //处理首列(i, 0)
    {
        if (g[i][0] != 1) //非马控制点
        {
            f[i][0] = 1;     //首列步数为1
        }
        else
        {
            for( ; i <= n; i++) //马控制点之后
            {
                f[i][0] = 0;    //首列所有点步数为0
            }
        }
    }
    for (j = 1; j <= m; j++)  //处理首行(0, j)
    {
        if (g[0][j] != 1) //非马控制点
        {
            f[0][j] = 1;    //首行步数为1
        }
        else
        {
            for( ; j <= m; j++) //马控制点之后
            {
                f[0][j] = 0;    //首行所有点步数为0
            }
        }
    }
    for (i = 1; i <= n; i++)  //非首行首列(i, j)
    {
        for (j = 1; j <= m; j++)
        {
            if (g[i][j] == 0) //非马控制点
            {
                f[i][j] = f[i-1][j] + f[i][j-1];    //(i, j)步数
            }
        }        //为其上点(i-1,j)与左点(i, j-1)步数和
    }
    printf("%d\n", f[n][m]);
    return 0;
}

代码改进:

引入两个一维数组dx 和 dy,分别用来统计从马的初始位置可以横向移动的位移与纵向移动的位移。

引入两个一维数组dx 和 dy
分别用来统计从马的初始位置可以横向移动的位移与纵向移动的位移。
#include //马踏过河卒
int main()
{
    int dx[9] = {0, -2, -1, 1, 2, 2, 1, -1, -2}; //C,P4,P3,P2,P1,
    int dy[9] = {0, 1, 2, 2, 1, -1, -2, -2, -1}; //P8, P7, P6, P5
    int n, m, x, y, i, j;
    int f[20][20] = {0}, g[20][20] = {0};  //初始化步数和马控制点
    scanf("%d%d%d%d", &n, &m, &x, &y);
    f[0][0] = 1;
    g[x][y] = 1;
    for(i =1; i <= 8; i++)  //循环处理马的8个控制点
    {
        if((x + dx[i] >= 0) && (x + dx[i] <= n)  //判断是否越界
                && (y + dy[i] >= 0) && (y + dy[i] <= m))
        {
            g[x + dx[i]][y + dy[i]] = 1;     //初始化马控制点
        }
    }
    for (i = 1; i <= n; i++)  //处理首列(i, 0)
    {
        if (g[i][0] != 1) //非马控制点
        {
            f[i][0] = 1;     //首列步数为1
        }
        else
        {
            for( ; i <= n; i++) //马控制点之后
            {
                f[i][0] = 0;    //首列所有点步数为0
            }
        }
    }
    for (j = 1; j <= m; j++)  //处理首行(0, j)
    {
        if (g[0][j] != 1) //非马控制点
        {
            f[0][j] = 1;    //首行步数为1
        }
        else
        {
            for( ; j <= m; j++) //马控制点之后
            {
                f[0][j] = 0;    //首行所有点步数为0
            }
        }
    }
    for (i = 1; i <= n; i++)  //非首行首列(i, j)
    {
        for (j = 1; j <= m; j++)
        {
            if (g[i][j] == 0) //非马控制点
            {
                f[i][j] = f[i-1][j] + f[i][j-1];    //(i, j)步数
            }
        }        //为其上点(i-1,j)与左点(i, j-1)步数和
    }
    printf("%d\n", f[n][m]);
    return 0;
}
#include    //马拦过河卒
#include 
#include 
//以向下为x轴正方向,以向右为y轴正方向建立直角坐标系
//(0, 0)为起点,(n, m)为终点, (h_x, h_y)为马的位置
int f[20][20],g[20][20];
int x[8] = {-2, -2, -1, -1, 1,  1, 2,  2};
int y[8] = {-1,  1, -2,  2, 2, -2, 1, -1};
int main()
{
    int n, m, h_x, h_y, i, j;
    scanf("%d %d %d %d", &n, &m, &h_x, &h_y);
    memset(f, 0, sizeof(f));
    memset(g, 0, sizeof(g));//两个数组都需要清零
    g[h_x][h_y] = 1;//马的位置,数组g所在位置赋为1
    f[0][0] = 1;//起始点,边界条件,路径数为1
    for(i = 0; i < 8; i++)
    {
        if((h_x + x[i] >= 0) && (h_x + x[i] <= n)
                &&(h_y + y[i] >= 0) && (h_y + y[i] <= m))
        {//判断马的控制点是否越界
            g[h_x + x[i]][h_y + y[i]] = 1;
        }//不越界的部分将马的控制点g[i][j]赋为1
    }
    for(i = 1; i <= n; i++)
    {//处理首列,上边界
        if(g[i][0] != 1)
        {//当边界点不为马的控制点时,该点有一个路径选择
            f[i][0] = 1;
        }
        else
        {//当边界点为控制点后,其点后面的点都没法走了
            break;//被马“截断”了
        }
    }
    for(i = 1; i <= m; i++)
    {//处理首行,左边界
        if(g[0][i] != 1)
        {//当边界点不为马的控制点时,有一个路径可以选择
            f[0][i] = 1;
        }
        else
        {
            break;
        }//理由同上
    }
    for(i = 1; i <= n; i++)
    {
        for(j = 1; j <= m; j++)
        {//一步步递推,直到递推到点(n, m)
            if(g[i][j] != 1)
            {//当不为马的控制点时
                f[i][j] = f[i - 1][j] + f[i][j - 1];
            }//(i, j)点的路径数为 左边+上边 点的路
        }
    }
    printf("%d\n", f[n][m]);
    return 0;
}

 

其他人关于本题的思考:

1. https://blog.csdn.net/crcr/article/details/6868853

2. https://www.cnblogs.com/fphuang/p/9694326.html

你可能感兴趣的:(递推递归,SDUT,OJ)