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