顺序: 暴力搜索 -> 记忆化搜索 -> 递推

记忆化搜索=暴力+记录答案
递推公式=dfs向下递归公式
递归数组初始值=递归的边界

用mem存已经有的值

[上台阶1] [c++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<bits/stdc++.h>
using namespace std;
long long n;
int mem[10000];

int dfs(int x)
{
if(mem[x]) return mem[x]; //如果已经有值 用现成的

int sum=0;
if(x==1) sum=1;
else if(x==2) sum=2;
else sum=dfs(x-1)+dfs(x-2);
mem[x]=sum;
return sum;
}
int main()
{
cin>>n;
int res=dfs(n);
cout<<res<<endl;
return 0;
}

递归中 递是自顶向下把大问题分解为子问题的过程 而归 是自底向上产生答案的过程
此处我们知道了递归搜索树底的答案
由此 我们可以自底向上递推出答案

[上台阶2] [c++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<bits/stdc++.h>
using namespace std;
int f[10000];
int n;

int main()
{
cin>>n;
f[1]=1;
f[2]=2;
if(n==1 || n==2)
{
cout<<f[n]<<endl;
return 0;
}
for(int i=3;i<=n;i++)
{
f[i]=f[i-1]+f[i-2];//也就是dfs的状态转移公式
}
cout<<f[n]<<endl;
return 0;
}

需要注意的是
1.想实现记忆化搜索 dfs参数需要尽可能少 因为记忆的时候若有n个参数 需开n维数组存
如这道题 如果额外一个参数sum记总共偷的钱 是不利于记忆化的
2.不应该把不影响到边界的参数放函数中
3.想剪枝那么得尽可能多放能剪枝的参数 (提前结束程序) 所以剪枝和记忆化搜索需要权衡

[acwing1049大盗阿福] [c++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<bits/stdc++.h>
using namespace std;
int home[10000];
int mem[10000];
int n,T;

int dfs(int x) //x表示正在考虑哪家店
{
if(mem[x]) return mem[x];
int sum=0;
if(x>n) sum=0; //没店就是0
else sum=max(dfs(x+1),dfs(x+2)+home[x]) //若不选第x家 则看x+1家 若选 则看往后第二家 值要加上home[x]

mem[x]=sum; //都是套路
return sum;
}

int main()
{
cin>>T;
while(T--)
{
cin>>n;
for(int i=1;i<=n;i++) cin>>home[i];
memset(mem,0,sizeof mem); //必须记得重置
int res=dfs(1);
cout<<res<<endl;
}
return 0;
}

mem[i] 存的是 从第i个店铺开始能获取到的最大价值(i-n)

上述都是递归到搜索树最底部再回溯 都是归的过程产生答案
而递推 我们则需要从最底部推回去 推回去 这一点很重要
总而言之就是递归搜索树的底部是什么状态 就要从什么状态开始
这道题的树的底部是搜索到了最后一家店 所以从n开始枚举
从n开始枚举 但方程不变

如果从1开始枚举 则1是底部 首先导入的元素是n 则为 f[i]=max(f[i-1],f[i-2]+home[i])
由于数组越界问题 f下标同时加2等效 f[i+2]=max(f[i+1],f[i]+home[i]) 此时f[i]存的是从1-i个店铺的劫掠最大值 f[n]为答案

[acwing1049大盗阿福2.0] [c++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<bits/stdc++.h>
using namespace std;
int home[10000];
int f[10000];

int n,T;
int main()
{
cin>>T;
while(T--)
{
cin>>n;
for(int i=1;i<=n;i++) cin>>home[i];
memset(mem,0,sizeof f); //必须记得重置
for(int i=n;i>=1;i--)
{
f[i]=max(f[i+1],f[i+2]+home[i]);
}
cout<<f[1]<<endl;
}
return 0;
}

求最优子问题 dfs(x)=max(dfs(x+1),dfs(x+2))
求子问题的和 dfs(x)=dfs(x+1)+dfs(x+2)

[P1216数字三角形1,。0] [c++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<bits/stdc++.h>
using namespace std;
int r;
int a[1010][1010];
int mem[1010][1010];

int dfs(int x1,int y1)
{
if(mem[x1][y1]) return mem[x1][y1];
int sum=0;
if(x>n || y> n) sum=0;
else sum=max(a[i+1][j],a[i+1][j+1])+g[i][j];
mem[x1]=sum;
return sum;
}

int main()
{
cin>>r;
for(int i=1;i<=r;i++)
{
for(int j=1;j<=i;j++) cin>>a[i][j];
}
int res=dfs(1,1);
cout<<res<<endl;
return 0;
}

写递推方程 递推方程便和dfs方程一样 但是需要从搜索树底部开始
即i从n开始枚举
如果要从1开始枚举 这道题中 最下面一行都有可能是终点 需要循环遍历得最大值

[P1216数字三角形2.0] [c++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<bits/stdc++.h>
using namespace std;
int r;
int a[1010][1010];
int f[1010][1010];
int main()
{
cin>>r;
for(int i=1;i<=r;i++)
{
for(int j=1;j<=i;j++) cin>>a[i][j];
}
for(int i=r;i>=1;i--)
{
for(int j=1;j<=r;j++)
{
f[i][j]=max(f[i+1][j],f[i+1][j+1])+a[i][j];
}

}
cout<<f[1][1]<<endl;
return 0;
}