对于一些刚学DP的同学,直接写出DP转移方程是有些困难的。这里张浩威(张过亿)dalao教给了我们一种简便的方法。可以先写出简单的暴力搜索,然后转成记忆化搜索。
优点:思路简单。
缺点:代码略难写,且很难优化。
例:01背包问题
首先可以写出最暴力的做法,即枚举每个物品选还是不选。
void dfs(int x,int y,int z) //x=当前物品编号,y=当前体积之和,z=当前价值之和 { if (x==n+1) { if (y<=m) ans=max(ans,z); return; } dfs(x+1,y+w[x],z+v[x]); dfs(x+1,y,z); } dfs(1,0,0); 时间复杂度为 O(2^n)
可以观察到当x和y固定时,z越大越好。
考虑将x y当成状态 z当成状态所代表的值。即dp[x][y]=z。然后把DFS的返回值改为int。
int dfs(int x,int y){ if(y>t) return -inf; if(x==n+1){ return 0; } if(dp[x][y]) return dp[x][y]; dp[x][y]=max(dfs(x+1,y),dfs(x+1,y+w[x])+v[x]); return dp[x][y]; }
x=当前物品编号 y=当前体积之和 dp[x][y]=接下来还能获取的最大价值 t=背包体积。
因为y>t的情况显然不合法,因此dp[x][y](y>t)= -inf 即这种情况绝对不会成为最优答案。
if(x==n+1) 递归边界 接下来还能获取的价值为0,因此返回0。
if(dp[x][y]) 说明之前曾经遍历过这种情况。因为该题的状态不会因为DFS的不同而改变,即DFS(3,2)与DFS(3,4)都有可能遍历到当前情况,但不会因DFS的不同而不同,因此可以直接返回之前被遍历到时的值。
dp[x][y]=max(dfs(x+1,y),dfs(x+1,y+w[x])+v[x]); 有两种决策:选与不选,因此dp[x][y]会有上面两个值转移到。需要注意,dp[][]表示的是接下来能获取的最大价值,所以别忘了加上当前的贡献。
这就是记忆化搜索。