Processing math: 100%

P7074 [CSP-J2020] 方格取数

Description

给定一个 nm 的矩阵,每个格子里有一个数,每次只能向上、下、右走一步,不能走走过的格子,问从左上角走到右下角可以取到的最大值。

Limit

n,m1000

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][j1]} + jS=ka[i][S]

temp=max(temp,f[i][j1])+a[i][j]

转移方程: f[i][j]=max(f[i][j],temp)

下面转移情况:

同上面转移,只不过反过来枚举 i

temp=max(temp,f[i][j1])+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(x1,y,0),dfs(x,y1,0),dfs(x,y1,1))+a[x][y]

这是上一步从上方转移的情况,由于不能走回头路,所以不能从下方转移。上面三个搜索分别是上方、左上方、左下方转移来。

  • f[x][y][1]=max(dfs(x+1,y,1),max(dfs(x,y1,0),dfs(x,y1,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;
}

posted @   panjx  阅读(884)  评论(0编辑  收藏  举报
编辑推荐:
· DeepSeek 解答了困扰我五年的技术问题
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
阅读排行:
· DeepSeek 解答了困扰我五年的技术问题。时代确实变了!
· PPT革命!DeepSeek+Kimi=N小时工作5分钟完成?
· What?废柴, 还在本地部署DeepSeek吗?Are you kidding?
· DeepSeek企业级部署实战指南:从服务器选型到Dify私有化落地
· 程序员转型AI:行业分析
点击右上角即可分享
微信分享提示