看了一些博客,总结出了记忆搜索和dp之间的一些关系
对于01背包这种类型的对于每个物品有选或者不选择两种状态的问题,我们先从暴搜入手思考这个问题
例如洛谷p1048
int n,t;
int tcost[103],mget[103];
int ans = 0;
void dfs( int pos , int tleft , int tans ){
if( tleft < 0 ) return;
if( pos == n+1 ){
ans = max(ans,tans);
return;
}
dfs(pos+1,tleft,tans);
dfs(pos+1,tleft-tcost[pos],tans+mget[pos]);
}
int main(){
cin >> t >> n;
for(int i = 1;i <= n;i++)
cin >> tcost[i] >> mget[i];
dfs(1,t,0);
cout << ans << endl;
return 0;
}
想不借助任何 "外部变量"(就是 dfs 函数外且 值随 dfs 运行而改变的变量 )记录答案
用返回值记录答案
int n,time;
int tcost[103],mget[103];
int dfs(int pos,int tleft){
if(pos == n+1)
return 0;
int dfs1,dfs2 = -INF;
dfs1 = dfs(pos+1,tleft);
if( tleft >= tcost[pos] )
dfs2 = dfs(pos+1,tleft-tcost[pos]) + mget[pos];
return max(dfs1,dfs2);
}
int main(){
cin >> time >> n;
for(int i = 1;i <= n;i++)
cin >> tcost[i] >> mget[i];
cout << dfs(1,time) << endl;
return 0;
}
对于相同的 pos 和 tleft,dfs 的返回值总是相同的!
想一想也不奇怪, 因为我们的 dfs 没有依赖任何外部变量.
像 tcost[103] , mget[103] 这种东西不算是外部变量, 因为她们在 dfs 过程中不变.
然后?
开个数组 mem , 记录下来每个 dfs(pos,tleft 的返回值. 刚开始把 mem 中每个值都设成 -1−1 (代表没访问过). 每次刚刚进入一个 dfs 前(我们的 dfs 是递归调用的嘛), 都检测 mem[pos][tleft] 是否为 -1−1 , 如果是就正常执行并把答案记录到 mem 中, 否则?
直接返回 mem 中的值!
int n,t;
int tcost[103],mget[103];
int mem[103][1003];
int dfs(int pos,int tleft){
if( mem[pos][tleft] != -1 ) return mem[pos][tleft];
if(pos == n+1)
return mem[pos][tleft] = 0;
int dfs1,dfs2 = -INF;
dfs1 = dfs(pos+1,tleft);
if( tleft >= tcost[pos] )
dfs2 = dfs(pos+1,tleft-tcost[pos]) + mget[pos];
return mem[pos][tleft] = max(dfs1,dfs2);
}
int main(){
memset(mem,-1,sizeof(mem));
cin >> t >> n;
for(int i = 1;i <= n;i++)
cin >> tcost[i] >> mget[i];
cout << dfs(1,t) << endl;
return 0;
}
此时 mem 的意义与 dfs 相同:
在时间 tleft 内采集 后 pos 个草药, 能获得的最大收益
总结一下记忆化搜索是啥:
不依赖任何 外部变量
答案以返回值的形式存在, 而不能以参数的形式存在(就是不能将 dfs 定义成 dfs(pos ,tleft , nowans ) , 这里面的 nowans 不符合要求).
对于相同一组参数, dfs 返回值总是相同的
记忆化搜索与动态规划的关系:记忆化搜索就是dp
在时间 tleft 内采集 后 pos个草药, 能获得的最大收益
这不就是dp的状态?
由上面的代码中可以看出:
dfs(pos,left)=max(dfs(pos+1,tleft−tcost[pos])+mget[pos] , dfs(pos+1,tleft)
即为
mem[pos][tleft]=max(mem[pos+1][tleft−tcost[pos]]+mget[pos] , mem[pos+1][tleft])
这不就是dp的状态转移?
总结一下:
记忆化搜索和动态规划从根本上来讲就是一个东西,任何一个 dp 方程都能转为记忆化搜索 ,反之亦然
体现在
根据记忆化搜索的参数可以直接得到dp的状态,反之亦然
根据记忆化搜索的递归关系可以写出状态转移方程,这个方程可以直接写出循环式的dp,只不过是反的(想想为什么?),反之亦然
大部分记忆化搜索时空复杂度与 不加优化的 dp 完全相同
最重要的一点:二者思想类似!! 核心思想均为:利用对于相同参数答案相同的特性,对于相同的参数(循环式的dp体现为数组下标),记录其答案,免去重复计算,从而起到优化时间复杂度的作用。这,便是二者的精髓。**
以上内容参考博客:https://interestinglsy.blog.luogu.org/memdfs-and-dp
看完大佬的博客后对dp的思想有了全新和更加深入的认识,然后我就在思考一个问题为什么同样的问题和方程dp可以用循环的方式实现记忆化搜索的这种每次递归两种状态的的模式呢?
先谈谈递归的实现方式可以用一颗二叉树的图形来概括
每个物品都可以分叉处选或者不选两个节点,pos 和 tleft对于搜索来说一定可以把pos~n的所有所用情况的跑到,所以记忆化搜索就可以把已经搜索到的pos和tleft先记录下来,遇到相同pos 和 tleft时候就可以不需要继续往下搜索直接得到值。
通过相同的思想联系到dp的实现,在状态转移的过程中,记忆化搜索是把已经选择好的状态(就是选择或者不选当前物品的状态)传递到下一层递归中,这样在这棵树的最下层节点中通过上层传递下来的状态,我再决定选择或者不选择这最后一个物品,将较大(最优情况)的值返回到上一层递归中,通过相同的方式传递到最上层的状态是否选择第一个物品,最后得出最大值。
举个栗子
让我假设现在的背包的容量是C=10;
物品编号: 1 2 3
物品重量: 5 6 4
物品价值:20 10 12
用搜索的思想看待的话我第一次传递就是我选择第一个物品pos+1,C-5和pos+1,C以此类推的向下传递状态,联想的dp也需要转移状态,但是我dp是通过两层循环实现的,就很难按照递归这种每次传递的都是我需要状态,怎么办呢?根据上文的提示说dp过程是反过来的,想到了,可以把第一个物品看作递归的最下层然后我向上传递状态,这个传递状态是我不清楚我上层对我有什么需求,我需要把我的当前的需求和上层因为不同的选择可能产生出的状态,全部考虑进去后,列出一个表格将这个表格整体传递给上一层(就是下一个物品的选择),说的比较拗口,将栗子中的dp数据列出来分析:
dp:0 0 0 0 0 0 0 0 0 0
i=1:
dp[10] = max(dp[5]+20, dp[10]);
dp[9] = max(dp[4]+20, dp[9]);
dp[8] = max(dp[3]+20, dp[8]);
dp[7] = max(dp[2]+20, dp[7]);
dp[6] = max(dp[1]+20, dp[6]);
dp[5] = max(dp[0]+20, dp[5]);
分析一下这行的dp数组,为什么dp数组中有4个0的情况呢,将0和20的情况分开看可联想到搜索中的选和不选当前物品的两种情况,那4个0分别就是,dp[1]在不选当前物品的情况下剩余9个空间,dp[2]在不选的情况下剩余8个空间....为什么要列出在不选择的情况下的所有情况呢?这里就是dp状态转移和搜索区别之处,因为搜索是由上向下的传递状态,所以我一定知道通过不同的组合我可以得到在一次选择中我之前选择会产生出哪几种状态,而dp是从下向上的啊,我只能列出所有状态让以后状态来选择要不要我向上传递的状态中的一个,继续列几组数据来看看:
i=2:
dp[10] = max(dp[6]+4, dp[10]);
dp[9] = max(dp[3]+10, dp[9]);
dp[8] = max(dp[2]+10, dp[8]);
dp[7] = max(dp[1]+10, dp[7]);
dp[6] = max(dp[0]+10, dp[6]);
dp: 0 0 0 0 20 20 20 20 20 20 //看到了没,选10的都被之前的20压下去了
再看第二组数据,这里也是选或者不选两种情况要考虑,因为物品的质量为6,所以只能从6开始选,那前面就是的dp[1]-dp[5]都是1和2都不选的情况,因为1和2没法同时选所以dp[6]-dp[10]是在选1和2里最大的一个情况,相当于用当前的2号把所有状态又更新了一遍
i=3:
dp[10] = max(dp[6]+12, dp[10]);
dp[9] = max(dp[5]+12, dp[9]);
dp[8] = max(dp[4]+12, dp[8]);
dp[7] = max(dp[3]+12, dp[7]);
dp[6] = max(dp[2]+12, dp[6]);
dp[5] = max(dp[1]+12, dp[5]);
dp[4] = max(dp[0]+12, dp[4]);
同理dp[3]也是用第三个物品的数据把整个数组更新了一遍,按照放或者不放从前面的状态中选择状态进行转移(已经最优),比较放或者不放对结果的影响,得出结果。
dp[10]就是背包容量为10的时候的最大价值,就是要求的值了