0
点赞
收藏
分享

微信扫一扫

[动态规划]DP13 [NOIP2002 普及组] 过河卒-中等

​​DP13 [NOIP2002 普及组] 过河卒​​

描述

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

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

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

[动态规划]DP13 [NOIP2002 普及组] 过河卒-中等_递归

注:马一次跳跃到达的点(x1,y1)和马原坐标(x,y)的关系是 



数据范围: ,马的坐标 

输入描述:

仅一行,输入 n,m,x,y 四个正整数。分别表示B点坐标和马的坐标

输出描述:

输出路径总数

示例1

输入:

6 6 3 3

复制

输出:

6

复制

示例2

输入:

5 4 2 3

复制

输出:

3

复制

示例3

输入:

2 5 3 5

复制

输出:

1

题解

假设我们使用box表示棋盘,box[x][y]表示棋盘在坐标(x,y)的状态:

  1. box[x][y]等于-2,表示该点是马的控制点
  2. box[x][y]等于-1表示到达该点的路径数还没有计算
  3. box[x][y]大于等于0表示到达该点的路径数

首先,我们申请一个n+1*m+1的棋盘,然后根据输入的马的坐标(x,y)在棋盘中将马的控制点设置为-2。由于棋子只能往下或者往右移动,因此box[x][y]的值仅仅依赖于box[x][y]自身的状态以及box[x-1][y]和box[x][y-1],有以下关系:

  1. 如果box[x][y]大于等于0,则表示该点的路径数已经计算过了,直接返回box[x][y]即可
  2. 如果box[x][y]等于-2,表示该点是马的控制点,肯定是0
  3. 否则box[x][y] = box[x-1][y] + box[x][y-1],表示可以从(x,y-1)和(x-1,y)两个坐标走到(x,y)

注意:

  1. 计算的结果用int存放最终有一个case没过,要改成int64_t


递归+记忆化解法

根据上面的分析,我们可以使用递归进行求解。我们定义函数walk(box,x,y)表示从坐标(0,0)走到坐标(x,y)共有多少种路径,那么如果要走到(n,m),那么可以直接返回walk(box,n,m)即可。如果在计算过程中,box[x][y]大于等于0则可以直接返回,表示已经求过该点的路径数。否则根据上面的分析进行递归计算。

代码如下:

#include <bits/stdc++.h>

using namespace std;

int64_t walk(std::vector<std::vector<int64_t>> &box, int64_t x, int64_t y)
{
if (x >= box.size() || y >= box[0].size())
{
return 0;
}

if (box[x][y] >= 0)
{
return box[x][y];
}

if (box[x][y] == -2)
{
return 0;
}

if (x == 0 && y == 0)
{
box[x][y] = 1;
}
else if (x == 0)
{
box[x][y] = walk(box, x, y - 1);
}
else if (y == 0)
{
box[x][y] = walk(box, x - 1, y);
}
else
{
if (box[x][y - 1] == -2 && box[x - 1][y] == -2)
{
box[x][y] = 0;
}
// else if (box[x][y - 1] == -2)
// {
// box[x][y] = walk(box, x - 1, y);
// }
// else if (box[x - 1][y] == -2)
// {
// box[x][y] = walk(box, x, y - 1);
// }
// else if (box[x - 1][y - 1] == -2)
// {
// box[x][y] = walk(box, x - 1, y) + walk(box, x, y - 1);
// }
else
{
box[x][y] = walk(box, x - 1, y) + walk(box, x, y - 1); // - walk(box, x - 1, y - 1);
}
}

return box[x][y];
}

int64_t solve(int64_t n, int64_t m, int64_t x, int64_t y)
{
// box[i][k]:-1表示还没有走过,-2表示马可以吃的坐标,大于等于0表示到达该点的总路径数
std::vector<std::vector<int64_t>> box(n + 1, std::vector<int64_t>(m + 1, -1));
std::vector<std::pair<int64_t, int64_t>> direct = {{-2, 1}, {-2, -1}, {-1, 2}, {-1, -2}, {1, 2}, {1, -2}, {2, 1}, {2, -1}, {0, 0}};
for (auto &d : direct)
{
int64_t a = x + d.first;
int64_t b = y + d.second;
if (a < 0 || a > n || b < 0 || b > m)
{
continue;
}
box[a][b] = -2;
}

if (box[n][m] == -2)
{
return 0;
}

return walk(box, n, m);
}

int main()
{
int64_t m, n, x, y;
std::cin >> n >> m >> x >> y;
std::cout << solve(n, m, x, y);
return 0;
}

动态规划解法

根据分析,我们可以定义dp[x][y]来表示走到该点的路径数,其意义和上面的分析一致。我们先从(0,0)开始遍历整个矩阵,然后根据dp[x][y]、dp[x-1][y]、dp[x][y-1]的关系求dp[x][y]的值,最后返回dp[n][m]即可。

代码如下:

#include <bits/stdc++.h>

using namespace std;

int64_t solve(int64_t n, int64_t m, int64_t x, int64_t y)
{
// box[i][k]:-1表示还没有走过,-2表示马可以吃的坐标,大于等于0表示到达该点的总路径数
std::vector<std::vector<int64_t>> box(n + 1, std::vector<int64_t>(m + 1, -1));
std::vector<std::pair<int64_t, int64_t>> direct = {{-2, 1}, {-2, -1}, {-1, 2}, {-1, -2}, {1, 2}, {1, -2}, {2, 1}, {2, -1}, {0, 0}};
for (auto &d : direct)
{
int64_t a = x + d.first;
int64_t b = y + d.second;
if (a < 0 || a > n || b < 0 || b > m)
{
continue;
}
box[a][b] = -2;
}

if (box[n][m] == -2 || box[0][0] == -2)
{
return 0;
}

box[0][0] = 1;
for (int i = 0; i <= n; ++i)
{
for (int k = 0; k <= m; ++k)
{
if (i == 0 && k == 0)
{
continue;
}
if (box[i][k] == -2)
{
continue;
}
if (i == 0)
{
box[i][k] = std::max(box[i][k - 1], (int64_t)0);
}
else if (k == 0)
{
box[i][k] = std::max(box[i - 1][k], (int64_t)0);
}
else
{
box[i][k] = std::max(box[i - 1][k], (int64_t)0) + std::max(box[i][k - 1], (int64_t)0);
}
}
}

return box[n][m];
}


int main()
{
int64_t m, n, x, y;
std::cin >> n >> m >> x >> y;
std::cout << solve(n, m, x, y);
return 0;
}


举报

相关推荐

0 条评论