P7074 [CSP-J2020] 方格取数
Description
给定一个 n∗m 的矩阵,每个格子里有一个数,每次只能向上、下、右走一步,不能走走过的格子,问从左上角走到右下角可以取到的最大值。
Limit
n,m≤1000。
Solution
Solution 1 DP
这题与某年前的方格取数很像,但由于这题可以上下走,所以有后效性,要转换思路。
发现虽然能上下乱跑,且走完一列之后只能向右走,所以可以把这个矩阵划分为 m 列,由此设方程。
又因为不能走回头路,所以在进入新的一列之后,要么只向上走,要么只向下走,要么再向右走一列。
设计状态:设 f[i][j] 表示在第i行,第j列的位置,且下一步必定向右走(即到 (i,j+1))可取到的最大价值。
很轻易地得到初始值:f[i][1]=∑iS=ja[j][1]
考虑每个点如何从前一列转移来:
据这个图可知,每个点从左边来有三种方式:
-
直接从左边走过来。(绿)
-
先从左边跳到这一列,再从上面走下来。(红)
-
先从左边跳到这一列,再从下面走上来。(蓝)
对这几种情况分别转移,为了转移方便,我们可以把左边的那一种情况给并到其他两种情况,答案不会改变。
上面转移情况:
可以从第 1 行枚举到 n 行,维护这一列的前缀和,则上面每一个得到的值就是前面的列中且结束在第i行的最大值+这一列向下走的过程中取的数之和。
设现在要求第 j 列第 i 行,已经枚举到了第k行,temp 就表示 max{f[i][j−1]} + ∑jS=ka[i][S]
即 temp=max(temp,f[i][j−1])+a[i][j]。
转移方程: f[i][j]=max(f[i][j],temp)。
下面转移情况:
同上面转移,只不过反过来枚举 i。
即 temp=max(temp,f[i][j−1])+a[i][j]
转移方程f[i][j]=max(f[i][j],temp)
Code:
// by youyou2007 Aug.
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <stack>
#define REP(i, x, y) for(register int i = x; i < y; i++)
#define rep(i, x, y) for(register int i = x; i <= y; i++)
#define PER(i, x, y) for(register int i = x; i > y; i--)
#define per(i, x, y) for(register int i = x; i >= y; i--)
#define lc (k << 1)
#define rc (k << 1 | 1)
using namespace std;
#define int long long//会爆int
const int N = 1005;
int n, m;
int f[N][N], a[N][N];
signed main()
{
scanf("%lld%lld", &n, &m);
rep(i, 1, n)
{
rep(j, 1, m)
{
scanf("%lld", &a[i][j]);
}
}
memset(f, -0x3f, sizeof f);
f[1][0] = 0;
f[1][1] = a[1][1];
rep(j, 1, m)
{
int temp = -99999999999999999;
rep(i, 1, n)
{
// if(i == 1 && j == 1) continue;
temp = max(temp, f[i][j - 1]) + a[i][j];
f[i][j] = max(f[i][j], temp);
}
temp = -9999999999999999;
per(i, n, 1)
{
// if(i == 1 && j == 1) continue;
temp = max(temp, f[i][j - 1]) + a[i][j];
f[i][j] = max(f[i][j], temp);
}
}
cout << f[n][m];
return 0
}
Solution 2 记忆化搜索
既然DP是顺推的,当然可以记忆化搜索逆推!
设 f[i][j][0] 表示当前在第 i 行第 j 列且从格子上(0)/下(1)方走到该格子的最大价值。
则上下的这两种情况可以解决了。
现在要考虑从左边过来的情况,但由于左边没有限制,所以从左上或左下来的转移都是可以的。
所以
- f[i][j][0]=max(dfs(x−1,y,0),dfs(x,y−1,0),dfs(x,y−1,1))+a[x][y]
这是上一步从上方转移的情况,由于不能走回头路,所以不能从下方转移。上面三个搜索分别是上方、左上方、左下方转移来。
- f[x][y][1]=max(dfs(x+1,y,1),max(dfs(x,y−1,0),dfs(x,y−1,1)))+a[x][y]
从下方转移同理,上面三个搜索分别是上方、左上方、左下方转移来。
Code
// by youyou2007 Aug.
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <queue>
#include <stack>
#define REP(i, x, y) for(register int i = x; i < y; i++)
#define rep(i, x, y) for(register int i = x; i <= y; i++)
#define PER(i, x, y) for(register int i = x; i > y; i--)
#define per(i, x, y) for(register int i = x; i >= y; i--)
#define lc (k << 1)
#define rc (k << 1 | 1)
using namespace std;
#define int long long
const int N = 1005;
const int MINN = -999999999999999;
int n, m;
int f[N][N][3], a[N][N];
int dfs(int x, int y, int from)
{
if(x <= 0 || y <= 0 || x > n || y > m) return MINN;
if(f[x][y][from] != MINN) return f[x][y][from];
if(from == 0)
{
return f[x][y][from] = max(dfs(x - 1, y, 0), max(dfs(x, y - 1, 0), dfs(x, y - 1, 1))) + a[x][y];
}
else
{
return f[x][y][from] = max(dfs(x + 1, y, 1), max(dfs(x, y - 1, 0), dfs(x, y - 1, 1))) + a[x][y];
}
}
signed main()
{
scanf("%lld%lld", &n, &m);
rep(i, 1, n)
{
rep(j, 1, m)
{
scanf("%lld", &a[i][j]);
f[i][j][0] = f[i][j][1] = MINN;
}
}
f[1][1][0] = f[1][1][1] = a[1][1];
dfs(n, m, 0);
cout << max(f[n][m][1], f[n][m][0]);
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· DeepSeek 解答了困扰我五年的技术问题。时代确实变了!
· PPT革命!DeepSeek+Kimi=N小时工作5分钟完成?
· What?废柴, 还在本地部署DeepSeek吗?Are you kidding?
· DeepSeek企业级部署实战指南:从服务器选型到Dify私有化落地
· 程序员转型AI:行业分析