地宫取宝,只需动态规划巨快

q在这里插入图片描述
第一行是网上普遍说的dfs+动态规划
第二行是只用动态规划解

显然,无论是时间还是代码长度,只用动态规划完胜啊兄弟们!

当然,我只想出部分,没写出来。于是看大佬题解,参悟甚多,然,大佬不重笔墨,把最关键的写了就走了,我在此为c语言网Nspyia大佬的题解写篇题解注。

如被Nspyia大佬看见这篇文章或者其它兄弟看见有不足之处。殷盼斧正。

正篇开始
在这里插入图片描述

动态规划无所谓两步:找到递推数组,找到递推公式。

如何找到递推数组

我一开始看到这题,就觉得它像洛谷的过河卒,就是那种对走路方向有规定的,走不回去的走法。
这种只许向下或向右走。基本递推式子:

dp[i][j]表示在(i,j)这个格子时总共的方案。在(i,j)点有可能从(i-1,j)向下走得到的,也可能从(i,j-1)向下走得到的。因此

dp[i][j]=dp[i-1][j]+dp[i][j-1]

要是这么简单就好了
在这里插入图片描述

为了让我们不好过,题目当然会给限制条件。

限制条件一:只能拿k件。
限制条件二:走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明才可以拿起它(当然,也可以不拿)

那数组不就出来了?嫩们想出来了吗?

dp[i][j][k][c]

动态规划另一个名字叫记忆化搜索。我认为记忆化搜索这个名字更适合人思考:

动态规划无非就是记忆化搜索,记忆化搜索,无非就是搜一个记一个,用什么记?数组。

也就是你处理这个题时,按照某个规律从第一步走到最后一步,每一步的结果都用数组记录。

数组出来了,那么dp[i][j][k][c]代表什么呢?

dp[i][j][k]很好解释,走到(i,j)的时候,手上共k件物品。

k就是用来计数的,没啥特别的。但是c不一样,它是需要比较的,它代表着一个临界

dp[i][j][k][c]:设dp[i][j][k][c]为走到(i,j)点,手上共K个物品,最大价值小于c的时候的不同方案种数(i,j从1开始计数)

int a[55][55]来存储格子上的宝物价值。

对于物品的递推关系:

对于(i,j)格子的物品拿还是不拿

拿(c>a[i][j]):dp[i][j][k][c]=dp[i-1][j][k-1][a[i][j]]+dp[i][j-1][k-1][a[i][j]]

不拿:dp[i][j][k][c]=dp[i-1][j][k][c]+dp[i][j-1][k][c]

不放就简单了,就啥都不干,加上上一步的两种走法即可。

写到这里觉得其实很简单了,就是在(i,j)处的物品那还是不拿,如果拿,有a种情况。不拿,有b种情况。那么对于(i,j)格子就有a+b种情况。

因为dp[i][j][k][c]记录的是要给下一步用的,而后面的步数是很有可能要拿物品的,所以c必须大于a[i][j]。

所以在放的代码 dp[i][j][k][c]=dp[i-1][j][k-1][a[i][j]]+dp[i][j-1][k-1][a[i][j]] 里的dp[i-1][j][k-1][a[i][j]]实质上是指a[i-1][j]物品拿和不拿的方案数量之和,
之所以不可以直接乘2是因为虽然不放没限制,但放有限制前面的物品价值必须比后面的物品价值都低才能拿,所以拿才要加入c>a[i][j]的判断。

同理,dp[i][j-1][k-1][a[i][j]]表示的是(i,j-1)格子拿和不拿的情况总和。因为要给dp[i][j][k][c]用,a[i][j]要放入,所以(i,j)以前的所有物品价值都得小于a[i][j]。

对于初始化:

对于第一个元素a[1][1]来说,也是两种情况,一放二不放。

一放:k==1且c>a[i][j] 即 if(k==1&&c>a[i][j]) dp[i][j][k][c]=1;

二不放:k==0 不放当然就是不放这一种情况
即 if(k==0) dp[i][j][k][c]=1;

两种情况合起来就是:

if(!k||k==1&&c>a[i][j]) dp[i][j][k][c]=1;

代码

#include <iostream>
  
using namespace std;
typedef long long LL;
LL dp[51][51][13][13],mod=1000000007;
int a[55][55];
int main() {
    int n,m,K;
    cin>>n>>m>>K;
    for(int i=0;i<n;i++)
	for(int j=0;j<m;j++)
		cin>>a[i+1][j+1];
		
    for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			for(int k=0;k<=K;k++)
				for(int c=0;c<=12;c++){
        LL na=0,buna=0;
        if(i==1&&j==1){
            if(!k||(k==1&&c>a[i][j]))dp[i][j][k][c]=1;
            continue; //continue是避免dp[1][1][k][c]的初始值被下面的代码改变。 
        }
        
        if(k&&c>a[i][j])na=dp[i-1][j][k-1][a[i][j]]+dp[i][j-1][k-1][a[i][j]];//拿(i,j)的物品,if条件里的k不为0必须写,不然k-1越界 
        buna=dp[i-1][j][k][c]+dp[i][j-1][k][c];//不拿(i,j)物品 
        dp[i][j][k][c]=na+buna;//拿和不拿的总情况 
        dp[i][j][k][c]%=mod;//结果可能很大,对结果取模 
    }
    cout<<dp[n][m][K][12]<<endl;
}

最后说一下,什么题都要有逆向思维。比如国王的烦恼,比如这道题。题目里说的是,必须后面的物品价值都比前面的价值都高才能拿。
那么反过来,就是前面的物品价值要都比后面的价值低才能拿,所以,a[i][j]<c时,才能拿a[i][j]。

不知道解释清楚了没有。希望对大伙有帮助吧…orz

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
鉴于提供的内容是一段包含三个不同的Java程序的代码,我无法提供一个完整的答案来回答你的问题。但是,我可以为你提供一些信息。 根据引用和引用的内容,这两个程序都涉及到动态规划算法。引用中的程序解决了一个关于序列的最长递增子序列的问题,而引用中的程序解决了一个关于背包问题的二维动态规划的问题。 对于引用中的内容,它是一个程序解决的是一个最长上升子序列的问题。 根据提供的代码,这些程序可能是用来解决一些蓝桥杯竞赛的题目。然而,我无法提供更多关于具体问题的信息。 如果你要帮助解决特定的问题,我建议你仔细阅读每个程序的代码并理解它们的功能,然后根据要进行相应的修改。如果你有关于动态规划蓝桥杯竞赛的更具体的问题,我会很愿意帮助你。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [蓝桥杯AcWing学习笔记 3-2简单DP的学习(附相关蓝桥真题:地宫取宝、波动数列)(Java)](https://blog.csdn.net/weixin_53407527/article/details/123122245)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值