【算法每日一练]-动态规划 篇10 方格取数 (两种解法)

高能预警:讲了这么久动态规划了,该上点有难度的题吧

目录

题目:方格取数

 思路(解法一):

 解法二:


        

        

题目:方格取数

        

 思路(解法一):

       

看上去有点复杂啊,每个格子都有3种情况,不太好处理。

那我们当然期望处理的只有两个方向。

       

因为只有两个方向的话(比如向右向下):

只需要设置f[i][j]表示到(i, j)的最大和,然后当然只能从(i-1, j)和(i, j-1)过来。

很容易得到转移方程f[i][j] = max(f[i-1][j],f[i][j-1]),

而且f[i-1][j]和f[i][j-1]都是已经确定的状态,

这样推下去,所有格子都会被算出来。

       

那么我们可不可以每列的的一个格子的三种状态拆开呢?拆成两个二状态的情况,向下向右,向上向右

      

         

        

我们设置 

up[i][j]代表从下面向上面走到(i,j)能取到的最大值;

down[i][j]代表从上面向下面走到(i,j)能取到的最大值;

f[i][j]表示(i,j)处最终可取到的最大值:

       
转移方程就好写了:

       

f[i][j] = max(up[i][j],down[i][j]);//取最大值呀  

up[i][j] = max(up[i+1][j],f[i][j-1])+a[i][j];//可以从左和下两个方向移动

down[i][j] = max(down[i-1][j],f[i][j-1])+a[i][j];//从左和上两个方向移动

     

因为数据比较大,所以我们要压缩成一维(因为我们在状态转移的时候只用到了j-1列的数据,故可以降一维,只需我们把列放外面即可。不明白的可以看动归最开始讲的那里)

    

故得到://这个f[i]是上一列的,循环时候把列放外面即可

    
up[i]=max(up[i+1],f[i])+a[i][j];//从下向上走

down[i]=max(down[i-1],f[i])+a[i][j];//从上向下走

f[i]=max(up[i],down[i])  //最终确定每个点的值
     

这个题之所以这么做很关键的一句话是:不能重复经过已经走过的地方,这也就明确了每一列其实只能有一种状态,要么向上要么向下。 

 最后就是要注意一个坑,最后一列,一定!一定!是向下走的,千万不要忽略了。

           

#include <bits/stdc++.h>  //(单向)方格取数
using namespace std;//动态规划
int n,m,a[1001][1001];
long long up[1005],down[1005],f[1005];
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]=a[1][1];
	for(int i=2;i<=n;i++) f[i]=f[i-1]+a[i][1];//初始化第一列的每行
	for(int j=2;j<m;j++){//每次用上一列的数据
		memset(down,0,sizeof(down));//我们只需要关注行的数据即可,故需要循环一次初始化一次
		memset(up,0,sizeof(up));
		down[1]=f[1]+a[1][j];up[n]=f[n]+a[n][j];
		for(int i=2;i<=n;i++) down[i]=max(f[i],down[i-1])+a[i][j];
		for(int i=n-1;i>=1;i--) up[i]=max(f[i],up[i+1])+a[i][j];
		for(int i=1;i<=n;i++) f[i]=max(down[i],up[i]);//一定要在down和up确定后再执行这个
	}
	f[1]+=a[1][m];//因为最后一列只能向下走,故单独处理
	for(int i=2;i<=n;i++) f[i]=max(f[i],f[i-1])+a[i][m];//向下处理,接受从左来的还是从上来的
	printf("%lld",f[n]);
	return 0;
}

       

解法二:

都说dp不好理解,那就再来个dfs吧(但我感觉我讲的已经很不错了哈)

      

             

还是相同的思想,把每列拆成两种情况:

我们设置 f(i, j, 0)表示从下面走到该各格子(i, j)对应最优解,f(i, j, 1)表示从上面走到该格子对应最优解。

那么递推公式:

f(i,j,0)=max( f(i+1,j,0) , f(i,j-1,0) , f(i,j-1,1) )+a[i][j]
f(i,j,1)=max( f(i-1,j,1)  , f(i,j-1,0) , f(i,j-1,1) )+a[i][j]

      
然后再加上记忆化的话一共只需要跑n*m*2即可获得所有结果,所以和dp一样快

流水的动归,铁打的递归
           

        

  再“保姆一点吧”,很多人不会写这个dfs函数。

首先说main函数:

观察递推公式,不难想到在main函数中要写dfs(n,m,1),因为最后一列必然是向下走的。然后去搜索记忆前面的状态。

       

然后因为题目中有烦人的负值,所以我们还需要初始化一下f函数成真正意义上的最小值,然后根据已经确定的状态初始化f[1][1][0] 和 f[1][1][1],这也成为了我们作为结束dfs的重要状态。

        

dfs函数:

dfs函数中呢,你完全可以先判断后dfs避免走到错误的状态,当然也可以检查是否走到了错误状态然后返回一个无效的值(我写的就是这个)。然后就是剪枝和记忆化返回。

        

        

#include <stdio.h>
typedef long long LL;  //记忆化搜索(和dp一样快)
const LL min_ll=-1e18;
int n,m;
LL w[1005][1005],f[1005][1005][2];
inline LL mx(LL p,LL q,LL r) {return p>q ? (p>r ? p:r) : (q>r ? q:r);}//三叶判断最快
inline LL dfs(int x, int y, int from) {
    if (x<1 || x>n || y<1 || y>m) return min_ll;
    if (f[x][y][from] != min_ll) return f[x][y][from];//记忆化
    if (from == 0) f[x][y][from] = mx(dfs(x+1,y,0), dfs(x,y-1,0), dfs(x,y-1,1))+w[x][y];
    else f[x][y][from] = mx(dfs(x-1,y,1), dfs(x,y-1,0), dfs(x,y-1,1))+w[x][y];
    return f[x][y][from];
}
int main(void) {
	scanf("%d %d", &n, &m);
	for (int i=1; i<=n; ++i)
		for (int j=1; j<=m; ++j) {
			scanf("%lld", &w[i][j]);
			f[i][j][0]=f[i][j][1]=min_ll;
		}
    f[1][1][0]=f[1][1][1]=w[1][1];//标记终点的值,到这个状态就直接返回,也就是dfs的结束条件
	printf("%lld\n",dfs(n,m,1));
	return 0;
}

好,那么好,如果你能看到这里,那么你真的很厉害了已经,下节讲双向类型的。

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值