DP13 [NOIP2002 普及组] 过河卒
描述
棋盘上 A点有一个过河卒,需要走到目标 B点。卒行走的规则:可以向下、或者向右。同时在棋盘上 C 点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。
棋盘用坐标表示,A 点 (0, 0)、B点(n,m),同样马的位置坐标是需要给出的。
现在要求你计算出卒从 A点能够到达 B点的路径的条数,假设马的位置(x,y)是固定不动的,并不是卒走一步马走一步。
注:马一次跳跃到达的点(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)的状态:
- box[x][y]等于-2,表示该点是马的控制点
- box[x][y]等于-1表示到达该点的路径数还没有计算
- 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],有以下关系:
- 如果box[x][y]大于等于0,则表示该点的路径数已经计算过了,直接返回box[x][y]即可
- 如果box[x][y]等于-2,表示该点是马的控制点,肯定是0
- 否则box[x][y] = box[x-1][y] + box[x][y-1],表示可以从(x,y-1)和(x-1,y)两个坐标走到(x,y)
注意:
- 计算的结果用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;
}