【20CSPJ普及组】方格取数(记忆化搜索)

原题链接

题目描述
设有 n×m 的方格图,每个方格中都有一个整数。现有一只小熊,想从图的左上角走到右下角,每一步只能向上、向下或向右走一格,并且不能重复经过已经走过的方格,也不能走出边界。小熊会取走所有经过的方格中的整数,求它能取到的整数之和的最大值。

输入
第一行有两个整数 n,m。
接下来 n 行每行 m 个整数,依次代表每个方格中的整数。

输出
一个整数,表示小熊能取到的整数之和的最大值。

输入样例

3 4
1 -1 3 2
2 -1 4 -1
-2 2 -3 -1

输出样例

9

记忆化搜索的优点
本题20个测试点总耗时1400ms,单个测试点最大耗时为320ms
记忆化搜索重要的一点是搞明白每个点的状态,当然这题直接给说了,每个点可能由左上下相邻的三个方向转移而来。
搞明白每个点状态及其状态转移,写个递归函数就能AC,还是很稳的

状态表示
ll dp[N][N][3] 对于每个点(x,y)
dp[x][y][0]代表从起点到(x,y)并且是由左侧一个点转移而来
dp[x][y][1]代表从起点到(x,y)并且是由上方一个点转移而来
dp[x][y][2]代表从起点到(x,y)并且是由下方一个点转移而来

当然这三个状态可以合并成两个。 先用三个状态方便理解。

状态转移
对于dp[x][y][0] , 显然x,y 这个点是由x,y-1 向右走一格而来,
那么dp[x][y][0] = max(dp[x][y-1][0],dp[x][y-1][1],dp[x][y-1][2]) 用到了x,y-1 的三个状态

同理,对于dp[x][y][1] , 显然x,y 这个点是由x-1,y 向下走一格而来,
那么dp[x][y][1] = max(dp[x-1][y][0],dp[x-1][y][1]) 用到了x-1,y 的两个状态
其中dp[x-1][y][2] 的这个状态对于 dp[x][y][1] 是非法的 ,因为不能先从x,y 向上到 x-1,y 又从x-1,y 向下到x,y

最后对于dp[x][y][2] , x,y 由x+1,y 向上走一格而来
dp[x][y][2] = max(dp[x+1][y][0],dp[x+1][y][2]) 用到了x+1,y 的两个状态
同理 dp[x+1][y][1]这个状态对于dp[x][y][2]是非法的

时间复杂度
由于需要计算每个点的三个状态
时间复杂度为 O(nm3) 也就是 O(nm)

初始化
由于是求最大值,先对整个dp[N][N][3] 数组赋 long long 下的极小值
注意这里不建议用memset函数

初始化起点,1,1 这个点显然不能从2,1向上走一格到达,因为起点就是1,1 一开始就被到达了
因此在dp[N][N][3]数组全为极小值的情况下
单独对 dp[1][1][0] = f[1][1] ,dp[1][1] = f[1][1];

其中f[N][N]数组是 每个点的权值

#include <bits/stdc++.h>
#define ull unsigned long long
#define ll long long
#define INF 0x3f3f3f3f
#define LINF 0x3f3f3f3f3f3f3f3f
using namespace std;
const int N = 1e3+10 ;
const ll MIN = -LINF;
int f[N][N];
ll dp[N][N][3];//由  右 上 下 转移而来 注意开long long
int n,m;
void init(){
    for(int i=0;i<N;i++){
        for(int j=0;j<N;j++){
            for(int k=0;k<3;k++){
                dp[i][j][k] = MIN;
            }
        }
    }
}

ll dfs(int x,int y,int k){
    if(x<1 or y<1 or x>n or y>m)return MIN;//越界直接返回极小值,代表非法状态转移
    if(dp[x][y][k]!=MIN)return dp[x][y][k];//!=极小值,代表被计算过,不必重复计算直接返回

    if(k==0){//对于每个点的每个状态都有转移方程
        dp[x][y][k] = max(dfs(x,y-1,2),max(dfs(x,y-1,0),dfs(x,y-1,1))) + (ll)f[x][y];
    }
    else if(k==1){
        dp[x][y][k] = max(dfs(x-1,y,1),dfs(x-1,y,0)) + (ll)f[x][y];
    }
    else {
        dp[x][y][k] = max(dfs(x+1,y,0),dfs(x+1,y,2)) + (ll)f[x][y];
    }
    
    return dp[x][y][k];
    
}

int main() {
    scanf("%d %d",&n,&m);

    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%d",&f[i][j]);
        }
    }
    init();//初始化
    dp[1][1][0] = (ll)f[1][1],dp[1][1][1] = (ll)f[1][1];

    ll ans = max(dfs(n,m,0),dfs(n,m,1)); //终点只能由左或上转移而来
    
    printf("%lld\n",ans);

    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值