记忆化的本质是:
先记录,后返回(记住:一定要记录,否则就是普通的递归);
查阅记录,如果记录中有,则直接返回。
下面通过几个简单的例子来深入了解一下
1.斐波那契
非记忆化
#include <bits/stdc++.h>
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 1e9+5;
ll dp[100];
ll F(int x)
{
if(x==1||x==2)
return 1ll;
return F(x-1)+F(x-2);
}
int main()
{
cout<<F(50)<<endl;
return 0;
}
记忆化搜索
#include <bits/stdc++.h>
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 1e9+5;
ll dp[100];
ll F(int x)
{
if(dp[x])
return dp[x];
if(x==1||x==2)
return 1ll;
dp[x]=F(x-1)+F(x-2);
return F(x-1)+F(x-2);
}
int main()
{
cout<<F(50)<<endl;
return 0;
}
2.求阶乘
非记忆化
#include <bits/stdc++.h>
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 1e9+5;
int dfs(int n)
{
if(n==1)return 1;
return n*dfs(n-1);
}
int main()
{
int n;
cin>>n;
cout<<dfs(n)<<endl;
return 0;
}
记忆化
#include <bits/stdc++.h>
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 1e9+5;
int dp[100];
int dfs(int n)
{
if(dp[n])return dp[n];
if(n==1)return 1;
dp[n]=n*dfs(n-1);
return n*dfs(n-1);
}
int main()
{
int n;
cin>>n;
cout<<dfs(n)<<endl;
return 0;
}
3.数的计数
我们要求找出具有下列性质数的个数,先输入一个自然数n,然后对此自然数按照如下方法进行处理:
*.不做任何操作
*.在它左边加上一个自然数,但该自然数不能超过原数的一半;
*.加上数后,继续按照此规则进行处理,直到不能再次加入自然数为止。
输入
8
输出
10
分析:
输入为8,输入的可能性为:
8
48
38
28
18
248
148
138
128
1248
非记忆化搜索
#include <bits/stdc++.h>
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 1e9+5;
//ll dp[100];
int dfs(int x)
{
int ans=1;
for(int i=1;i<=x/2;i++)
{
ans+=dfs(i);
}
return ans;
}
int main()
{
int n;
cin>>n;
cout<<dfs(n)<<endl;
return 0;
}
记忆化搜索
#include <bits/stdc++.h>
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 1e9+5;
int dp[100];
int dfs(int x)
{
int ans=1;
if(dp[x])return dp[x];
for(int i=1;i<=x/2;i++)
{
ans+=dfs(i);
}
dp[x]=ans;
return ans;
}
int main()
{
int n;
cin>>n;
cout<<dfs(n)<<endl;
return 0;
}
4.切棒子
给你一根长n英尺的棒子和一份关于该棒子的价目表如下(其中 i = 1,2,3,…,n),请问如何将这根棒子卖出最高的价格,可以对棒子进行切割。
输入
10
1 5 8 9 10 17 17 20 24 30
输出
30
递推公式为DP(n)=optimal{max{price(i)+DP(n-i)|1≤i≤n}}。
非记忆化
#include <bits/stdc++.h>
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 1e9+5;
const int inf=1<<30;
int dp[100];
int prime[100];
int dfs(int n)
{
if(n==0)return 0;
int ans=-inf;
for(int i=1;i<=n;i++)
{
ans=max(ans,dfs(n-i)+prime[i]);
}
return ans;
}
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)cin>>prime[i];
printf("%d\n",dfs(n));
return 0;
}
记忆化
#include <bits/stdc++.h>
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 1e9+5;
const int inf=1<<30;
int dp[100];
int prime[100];
int dfs(int n)
{
if(dp[n])return dp[n];
if(n==0)return 0;
int ans=-inf;
for(int i=1;i<=n;i++)
{
ans=max(ans,dfs(n-i)+prime[i]);
}
dp[n]=ans;
return ans;
}
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)cin>>prime[i];
printf("%d\n",dfs(n));
return 0;
}
5. 01背包
问题描述: 有N件物品和一个容量为V的背包。(每种物品均只有一件)第i件物品的重量是w[i],价值是v[i]。求解将哪些物品装入背包可使价值总和最大。
DP方法
#include <bits/stdc++.h>
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 1e9+5;
const int inf=1<<30;
int dp[100];
int w[105];
int v[105];
int main()
{
int N,V;
cin>>N>>V;
for(int i=1;i<=N;i++)cin>>w[i];
for(int i=1;i<=N;i++)cin>>v[i];
for(int i=1;i<=N;i++)
for(int j=V;j>=w[i];j--)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
printf("%d\n",dp[V]);
return 0;
}
记忆化
#include <bits/stdc++.h>
#define ms(a,b) memset(a,b,sizeof(a))
typedef long long ll;
using namespace std;
const int maxn = 1e9+5;
const int inf=1<<30;
int dp[105][105];
int w[105];
int v[105];
int dfs(int i,int V)
{
if(dp[i][V])return dp[i][V];
if(i==0 || V<=0)return 0;
if(w[i]>V)dp[i][V]=dfs(i-1,V);
else dp[i][V]=max(dfs(i-1,V),dfs(i-1,V-w[i])+v[i]);
return dp[i][V];
}
int main()
{
int N,V;
cin>>N>>V;
for(int i=1; i<=N; i++)cin>>w[i];
for(int i=1; i<=N; i++)cin>>v[i];
cout<<dfs(N,V)<<endl;
return 0;
}
最后我总结了一个模板(以数的计数为例)
dfs(问题)
{
if(a已解)//if(dp[x])return dp[x];
然后:
查阅记录。
else//得到问题a的最优解。
将问题a分成几个子问题(a1,a2,…,ak)
用dfs(a1)、dfs(a2)、…、dfs(ak)求出问题a的解。 //for(int i=1;i<=x/2;i++){ans+=dfs(i);}
最后将最优解写入记录。dp[x]=ans;
}
总结:
记忆化搜索实际上就是在普通搜索的基础上加了一个记录(备忘录),每次选择dfs的时候如果发现当前路径以前走过那么就可以省下向下的的过程的、,大大缩减了时间,提高了效率