洛谷 P7074 [CSP-J2020] 方格取数(T4)

目录

题目传送门

25分算法

思路 

参数

边界及存储最大值

枚举行走方向

向行驶方向走

递归

回溯

主函数

代码

提交结果

AC 算法

思路

参数

边界

记忆化节省时间

寻找最大值

left

up

down

返回值

代码

提交结果

尾声


题目传送门

[CSP-J2020] 方格取数 - 洛谷icon-default.png?t=N7T8https://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 ~ n1 ~ 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~

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值