目录
题目传送门
[CSP-J2020] 方格取数 - 洛谷https://www.luogu.com.cn/problem/P7074
25分算法
这道题呢,是一道很明显的 dp 题,但是我们先讲一下宝贵的部分分
部分分也很重要!!!毕竟有的同学还不会动规!
思路
emmm……不用 dp 的话……也许可以用 dfs!
参数
dfs 的参数嘛,首先需要记录横坐标和纵坐标,其次,要记录到目前为止的数字之和
void dfs(int x, int y, int s)
其中 x 和 y 代表横坐标和纵坐标,s 则记录到目前为止的数字之和
边界及存储最大值
递归,一定要有递归边界
这次的递归边界显然是到达终点,即
if(x == n && y == m)
这个时候,我们就可以记录最大值了
可以定义全局变量
#define inf 10005
int maxx = -inf;
这样,我们就可以在到达边界时记录最大值了
if(x == n && y == m)
maxx = max(maxx, s);
枚举行走方向
然后,我们就可以开始枚举行驶方向了
可以定义方向数组,向上、下、右去走
const int xx[3] = {-1, 1, 0};
const int yy[3] = {0, 0, 1};
这样,我们用一个 for 循环,从 0 到 2 枚举方向
要去的地点就是 (x + xx[i], y + yy[i])
令要去的点的横纵坐标为 (x1, y1),则代码实现为
int x1 = x + xx[i];
int y1 = y + yy[i];
然后,我们要查看这个点是否越界、之前是否走过
查看是否越界很简单,就判断是否在 1 ~ n 、1 ~ m 里面就行了
if(x1 > 0 && x1 <= n && y1 > 0 && y1 <= m)
查看之前是否走过,就需要用到一个标记数组
#define N 1005
bool f[N][N];
f[x][y] 表示坐标为 (x, y) 的点有没有走过(有为 1,没有为 0)
然后我们判断这个点有没有走过就直接判断 f[x][y] 是 1 还是 0 就行了
最终的判断
if(x1 > 0 && x1 <= n && y1 > 0 && y1 <= m && !f[x1][y1])
向行驶方向走
递归
首先,要将该点设为走过
f[x1][y1] = 1;
其次就是递归了
递归他走到的那个点,(x1, y1)
然后数字之和加上该点的数字 a[x1][y1]
dfs(x1, y1, s + a[x1][y1]);
回溯
注意,该点走完后,返回到这里,需将该点设为没走过
f[x1][y1] = 0;
主函数
这里很重要!!!
1、起点一定要设成走过
f[1][1] = 1;
2、传进去的横纵坐标为 (1, 1),数字和为起点的值 a[1][1]
dfs(1, 1, a[1][1]);
代码
这种做法的代码如下
#include <iostream>
#include <algorithm>
#define N 1005
#define inf 10005
using namespace std;
const int xx[3] = {-1, 1, 0};
const int yy[3] = {0, 0, 1};
int n, m, a[N][N], maxx = -inf;
bool f[N][N];
void dfs(int x, int y, int s) {
if(x == n && y == m)
maxx = max(maxx, s);
else
for(int i = 0; i < 3; ++i) {
int x1 = x + xx[i];
int y1 = y + yy[i];
if(x1 > 0 && x1 <= n && y1 > 0 && y1 <= m && !f[x1][y1]) {
f[x1][y1] = 1;
dfs(x1, y1, s + a[x1][y1]);
f[x1][y1] = 0;
}
}
}
int main() {
cin >> n >> m;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
cin >> a[i][j];
f[1][1] = 1;
dfs(1, 1, a[1][1]);
cout << maxx << endl;
return 0;
}
提交结果
提交一下~
25分 TLE!
AC 算法
思路
上面说过了,我们正规做法应该是 dp !
我们这道题呢,满足无后效性,即无论从哪个点到这个点,后面的最大的和都一样
可以用记忆化搜索
用数组 f[x][y] 来记录从这个点到终点的最大数字和
但是仔细思考一下,这样做有个问题
题目中说“不能重复经过已经走过的方格”如果这个点是从上面来的,会搜索到上面,所以我们可以通过改变搜索顺序和标记这个点 不能向上搜(不是从上面来的)还是 不能向下搜 (不是从下面来的)
为什么不考虑是从左边过来的呢?因为题目中说“每一步只能向上、向下或向右走一格”,没说可以向左走,所以不用考虑
1、改变搜索顺序
可以从右下到左上,这样深搜可以返回一个值:从此点到起点的最大和
2、标记
可以让数组增维
#define inf 100000000000000ll
typedef long long ll;
ll f[N][N][2];
其中,f[x][y][0] 表示该点不是从上来的时候的最大和,f[x][y][1] 表示该点不是从下来的时候的最大和(因为是倒推)
参数
首先需要记录横坐标和纵坐标(x 和 y),其次,要记录 k,表示该点不是从上来的(0)或不是从下来(1)
ll dfs(int x, int y, int k)
有一点需要注意,主函数里传的横纵坐标就是 (n, m),这样输出的就是从终点到起点的最大和,k 呢,则是 1,因为终点不能再向下走了,所以应该传 1
cout << dfs(n, m, 1) << endl;
边界
边界就是搜到起点了,即
if(x == 1 && y == 1)
这时,f[x][y][k] 就等于 a[x][y],返回值也是 a[x][y],因为起点到起点的最大和就是起点
if(x == 1 && y == 1) {
f[x][y][k] = a[x][y];
return a[x][y];
}
记忆化节省时间
我们记忆化搜索,就是为了节省时间
怎么节省呢,就是判断这次的 f 有没有被赋过值
我们在输入时就把他们都赋成了最小值
#define inf 100000000000000ll
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j) {
cin >> a[i][j];
f[i][j][0] = f[i][j][1] = -inf;
}
所以判断如果这次的 f 不是 -inf,就返回他
if(f[x][y][k] != -inf)
return f[x][y][k];
寻找最大值
现在,我们的任务就是把最大值找出来
我们用 ans 来存储最大值
ll ans = -inf;
left
首先是左边,因为左边是无论如何都可以走的(上面解释过了),所以不用特判
左边的左面,上面,右面都可以走,所以搜左边时,就找 k 是 0 大还是 1 大就行
左边的坐标就是 (x, y - 1),注意,不能越界,即 y 必须大于 1(这样减 1 后就不会小于等于 0)
这样,就找他左边到起点的最大值,再加上现在的值,意思就是另外一种表示他本身到起点的最大值的方法
if(y > 1) {
bemax(ans, dfs(x, y - 1, 1) + a[x][y]);
bemax(ans, dfs(x, y - 1, 0) + a[x][y]);
}
里面的 bemax 是我自己写的,意思是把两个中最大的赋给第一个
void bemax(ll &a, ll b) {
a = max(a, b);
}
up
上面的,首先需要判断他能向上(即不是从下面过来的)
if(k)
其次,要判断没有越界,即 x > 1,因为向上,x 需要减 1
if(x > 1)
最后,就可以找最大的了,即 ans = max(ans, dfs(x - 1, y, 1) + a[x][y]),为什么 k 是 1 呢,因为上次是向下(顺推情况下),所以他不是从下面来的
bemax(ans, dfs(x - 1, y, 1) + a[x][y]);
down
跟 up 类似,这里就不多解释了,浪费篇幅
if(!k) {
if(x < n)
bemax(ans, dfs(x + 1, y, 0) + a[x][y]);
}
返回值
最后,我们找到了最大值 -- ans,我们把他赋给 f[x][y][k],然后返回值就是他
f[x][y][k] = ans;
return ans;
代码
最终的 AC 代码来了!!!
#include <iostream>
#include <algorithm>
#define N 1005
#define inf 100000000000000ll
using namespace std;
typedef long long ll;
ll a[N][N], f[N][N][2];
int n, m;
void bemax(ll &a, ll b) {
a = max(a, b);
}
ll dfs(int x, int y, int k) {
// 0 : 不是从上面来
// 1 : 不是从下面来
ll ans = -inf;
if(x == 1 && y == 1) {
f[x][y][k] = a[x][y];
return a[x][y];
}
if(f[x][y][k] != -inf)
return f[x][y][k];
if(y > 1) {
bemax(ans, dfs(x, y - 1, 1) + a[x][y]);
bemax(ans, dfs(x, y - 1, 0) + a[x][y]);
}
if(k) {
if(x > 1)
bemax(ans, dfs(x - 1, y, 1) + a[x][y]);
} else {
if(x < n)
bemax(ans, dfs(x + 1, y, 0) + a[x][y]);
}
f[x][y][k] = ans;
return ans;
}
int main() {
cin >> n >> m;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j) {
cin >> a[i][j];
f[i][j][0] = f[i][j][1] = -inf;
}
cout << dfs(n, m, 1) << endl;
return 0;
}
提交结果
提交一下~
AC!!!
尾声
如果这篇不长超长的题解对您(您的团队)有帮助的话,就帮忙点个赞,加个关注!
最后,祝您(您的团队)在 OI 的路上一路顺风!!!
─=≡Σ(((つ•̀ω•́)つ Bye~Bye~