P1002 过河卒题解
一、深搜尝试
上来简单一看,无脑暴搜开始!
注意
//马所在的位置
ctrl[x][y] = 1;
ctrl[x - 1][y - 2] = 1;
ctrl[x - 2][y - 1] = 1;
ctrl[x + 1][y + 2] = 1;
ctrl[x + 2][y + 1] = 1;
ctrl[x - 2][y + 1] = 1;
ctrl[x - 1][y + 2] = 1;
ctrl[x + 1][y - 2] = 1;
ctrl[x + 2][y - 1] = 1;
这里面有一个细节,有的网友在计算马的控制范围时,简单粗暴的使用了上面的代码,这是不对的,因为没有判断\(x+2\)会不会越界,\(x-2\)会不会越界等,会有问题的。
正确的初始化马的位置代码如下:
//增量数组,delta
int d[8][2] = {
{1, 2},
{1, -2},
{-1, 2},
{-1, -2},
{2, 1},
{2, -1},
{-2, 1},
{-2, -1}};
//马的实际控制范围
for (int i = 0; i < 8; i++) {
int tx = mx + d[i][0], ty = my + d[i][1];
if (tx >= 0 && tx <= n && ty >= 0 && ty <= m) ctrl[tx][ty] = 1;
}
//马所在的位置你也不能走,也踢你~
ctrl[mx][my] = 1;
完整的dfs代码如下:
dfs1
#include <bits/stdc++.h>
using namespace std;
const int N = 22;
int ctrl[N][N];
int cnt, n, m;
int mx, my;
//深搜
void dfs(int x, int y) {
//终点
if (x == n && y == m) {
cnt++;//统计数增加1
return;
}
//如果不能走
if (x > n || y > m || ctrl[x][y]) return;
//如果能走
dfs(x + 1, y);//向右
dfs(x, y + 1);//向下
}
//增量数组,delta
int d[8][2] = {
{1, 2},
{1, -2},
{-1, 2},
{-1, -2},
{2, 1},
{2, -1},
{-2, 1},
{-2, -1}};
int main() {
//读入B点坐标和马的坐标
cin >> n >> m >> mx >> my;
//马的实际控制范围
for (int i = 0; i < 8; i++) {
int tx = mx + d[i][0], ty = my + d[i][1];
if (tx >= 0 && tx <= n && ty >= 0 && ty <= m) ctrl[tx][ty] = 1;
}
//马所在的位置你也不能走,也踢你~
ctrl[mx][my] = 1;
//深搜
dfs(0, 0);
//输出
printf("%d\n", cnt);
}
dfs2
#include <bits/stdc++.h>
using namespace std;
const int N = 22;
int ctrl[N][N];
int n, m;
int mx, my;
//深搜
int dfs(int x, int y) {
//终点
if (x == n && y == m) return 1;
//不能走
if (ctrl[x][y] || x > n || y > m) return 0;
//向右 向下
return dfs(x + 1, y) + dfs(x, y + 1);
}
//增量数组,delta
int d[8][2] = {
{1, 2},
{1, -2},
{-1, 2},
{-1, -2},
{2, 1},
{2, -1},
{-2, 1},
{-2, -1}};
int main() {
//读入B点坐标和马的坐标
cin >> n >> m >> mx >> my;
//马的实际控制范围
for (int i = 0; i < 8; i++) {
int tx = mx + d[i][0], ty = my + d[i][1];
if (tx >= 0 && tx <= n && ty >= 0 && ty <= m) ctrl[tx][ty] = 1;
}
//马所在的位置你也不能走,也踢你~
ctrl[mx][my] = 1;
//深搜
int sum = dfs(0, 0);
//输出
printf("%d\n", sum);
}
这么写完一提交,TLE了两个测试点。浪费一下下载机会,看一下,发现是极限数据,棋盘大小:\(n=20,m=20\),超时了。
2、为什么深搜会超时
都知道这道题数据量\(n,m<=15\)时可以使用深搜和递推,\(n,m>15\)时需要使用递推,那为什么深搜搞不定呢?理由:
上面是我想的原因,大神勿喷啊!重复计算才是万恶之首啊!!那解决方案也就到来了~(1、记忆化搜索,2、递推)
3、记忆化搜索
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 22;
int n, m, mx, my;
LL dp[N][N]; //一定要开long long ,不然3 4 测试点过不了
//增量数组,delta
int d[8][2] = {
{1, 2},
{1, -2},
{-1, 2},
{-1, -2},
{2, 1},
{2, -1},
{-2, 1},
{-2, -1}};
int ctrl[N][N];
//搜索
LL dfs(int x, int y) {
//计算过,直接返回
if (dp[x][y]) return dp[x][y];
//走不了
if (ctrl[x][y] || x > n || y > m) return dp[x][y] = 0;
//如果是终点
if (x == n && y == m) return dp[x][y] = 1;
//如果都不是,那么需要依赖于右和下的和
return dp[x][y] = dfs(x, y + 1) + dfs(x + 1, y);
}
int main() {
cin >> n >> m >> mx >> my;
//马的实际控制范围
for (int i = 0; i < 8; i++) {
int tx = mx + d[i][0], ty = my + d[i][1];
if (tx >= 0 && tx <= n && ty >= 0 && ty <= m) ctrl[tx][ty] = 1;
}
//马所在的位置你也不能走,也踢你~
ctrl[mx][my] = 1;
//开始深搜
cout << dfs(0, 0);
return 0;
}
4、递推(动态规划)
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 22;
LL f[N][N]; //递推小心爆INT,不开LL见祖宗
int ctrl[N][N]; //定义马的控制范围,一般采用int数组进行状态标识,不采用bool,因为1,0比true和false写的快
int n, m, x, y; //B点(目标点)的坐标,马的坐标
//增量数组,delta
int d[8][2] = {
{1, 2},
{1, -2},
{-1, 2},
{-1, -2},
{2, 1},
{2, -1},
{-2, 1},
{-2, -1}};
int main() {
//读入B点坐标和马的坐标
cin >> n >> m >> x >> y;
//马的实际控制范围
for (int i = 0; i < 8; i++) {
int tx = x + d[i][0], ty = y + d[i][1];
if (tx >= 0 && tx <= n && ty >= 0 && ty <= m) ctrl[tx][ty] = 1;
}
//马所在的位置你也不能走,也踢你~
ctrl[x][y] = 1;
//如果原点在马的控制范围内,那么就是无法出发
//如果原点不在马的控制范围内,那么就是有一种方法
if (ctrl[0][0]) f[0][0] = 0;
else f[0][0] = 1;
//开始递推
for (int i = 0; i <= n; i++) //遍历整个棋盘
for (int j = 0; j <= m; j++) {
//在马的控制范围内,就放弃掉这个点,路线条数为0,默认就是0,不需要改变,直接 continue即可。
if (ctrl[i][j])continue;
//不是第0行,可以从上一行递推过来
if (i > 0) f[i][j] += f[i - 1][j];
//不是第0列,可以从上一列递推过来
if (j > 0) f[i][j] += f[i][j - 1];
}
//输出结果
cout << f[n][m];
}