interestingLSY
2018-08-23 10:57:49
by
想体验把暴搜改改就是正解的快感吗? 想体验状压dp看似状态多到爆炸实际一跑却嗷嗷快(实际有效的状态数很少)的荣耀吗? 记忆化搜索,符合您的需求!只要998,记忆化搜索带回家!记忆化搜索,记忆化搜索,再说一遍,记忆化搜索!
先点个赞吧(逃
由于我讲的比较磨叽,兜售目录一份,dalao们可以选择自己喜欢的部分看
记忆化搜索是啥
记忆化搜索和动态规划有啥关系
如何写记忆化搜索
记忆化搜索的优缺点
记忆化搜索的注意事项
相关文章推荐
好,就以 这道题 为例,我不会动态规划,只会搜索,我就会直接写一个粗暴的 DFS :
注: 为了方便食用, 本文中所有代码省略头文件
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;
}
这就是个十分智障的大暴搜是吧......
emmmmmm.......
然后我心血来潮, 想不借助任何 "外部变量"(就是 dfs 函数外且 值随 dfs 运行而改变的变量 ), 比如 ans
把 ans 删了之后就有一个问题: 我们拿什么来记录答案?
答案很简单:
返回值!
此时
不理解就看看代码吧:
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;
}
emmmmmm....... 还是
但这个时候, 我们的程序已经不依赖任何外部变量了.
然后我非常无聊, 将所有 dfs 的返回值都记录下来, 竟然发现......
震惊, 对于相同的 pos 和 tleft,dfs 的返回值总是相同的!
想一想也不奇怪, 因为我们的 dfs 没有依赖任何外部变量.
旁白: 像
然后?
开个数组
直接返回
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;
}
此时
在时间
tleft 内采集 后pos 个草药, 能获得的最大收益
这能 ac?
能. 这就是 "采药" 那题的 AC 代码
好我们 yy 出了记忆化搜索
不依赖任何 外部变量
答案以返回值的形式存在, 而不能以参数的形式存在(就是不能将 dfs 定义成
对于相同一组参数, dfs 返回值总是相同的
基本是朋 (ji) 友关系
有人会问: 记忆化搜索难道不是搜索?
一定程度上来说,她是搜索.但个人认为她更像dp
其实说白了,记忆化搜索就是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 完全相同
建议好好想想第四条。记住,学一个算法,一定要理解他的精髓。
举个栗子:
转为
int dfs( int i , int j , int k ){
边界条件
if( mem[i][j][k] != -1 ) return mem[i][j][k];
return mem[i][j][k] = dfs(i+1,j+1,k-a[j]) + dfs(i+1,j,k);
}
int main(){
memset(mem,-1,sizeof(mem));
读入
cout << dfs(1,0,0) << endl;
}
二者满足上面提到的所有关系
举例:
但是!如果我们用记忆化搜索,就可以避免到很多无用的状态,比如
然后就
举例: 用常规 dp 写"合并石子"需要先枚举区间长度然后枚举起点,但记忆化搜索直接枚举断点(就是枚举当前区间由哪两个区间合并而成)然后递归下去就行
边界情况非常好处理, 且能有效防止数组访问越界
写起来简单易懂 至少我镇么认为 qwq
有些 dp(如区间 dp)用记忆化搜索写很简单但正常 dp 很难
记忆化搜索天生携带搜索天赋,可以使用技能"剪枝"!
缺点:
致命伤: 不能滚动数组!(哪位 dalao 会记搜 + 滚动的请在评论区留名)
有些优化比较难加
由于递归, 有时效率较低但不至于 TLE (状压dp除外)
代码有点长其实也不算太长
千万别忘了加记忆化! (别笑, 认真的
边界条件要加在检查当前数组值是否为 - 1 前(防止越界)
数组不要开小了(逃
《挑战程序设计竞赛》(又名白书),对动态规划的引入的那一章(它也是从暴搜开始讲起)
questions are welcome