🧠 记忆化搜索 完整代码
我们会用一个 memo
数组来存储每个状态的计算结果,避免重复计算。用 visited
数组来标记是否已经访问过某个状态。
完整代码如下
#include <stdio.h>
#define MOD 1000000007 // 模数,防止答案过大
// 定义存储每个状态的计算结果和访问标记的数组
int memo[101][101][101]; // 三维数组来存储 (m, f, j) 的结果
int visited[101][101][101]; // 三维数组来标记 (m, f, j) 是否已访问过
// dfs:从状态 (m, f, j) 递归计算结果
int dfs(int m, int f, int j) {
// 如果酒已经喝光并且没有剩余的店和花
if (m == 0 && f == 1 && j == 1) return 1; // 基本情况,走到最终状态
// 剪枝:如果酒的数量大于花的数量,说明不可能继续走下去
if (j > f) return 0;
// 如果这个状态已经计算过,直接返回之前存的结果
if (visited[m][f][j]) return memo[m][f][j];
// 标记这个状态已经访问过
visited[m][f][j] = 1;
int ans = 0;
// 递归调用:如果还有花,喝一斗酒(遇到花)
if (f > 1) {
ans += dfs(m, f - 1, j - 1); // 遇到花,酒减少1,花减少1
}
// 递归调用:如果还有店,酒加倍(遇到店)
if (m > 0) {
ans += dfs(m - 1, f, j * 2); // 遇到店,酒加倍,店减少1
}
// 结果记忆化:计算结果对 MOD 取模,防止数字过大
memo[m][f][j] = ans % MOD;
return memo[m][f][j]; // 返回计算的结果
}
int main() {
int m, f, j;
scanf("%d %d %d", &m, &f, &j);
// 初始化:将所有访问标记为 0,表示没有计算过
for (int i = 0; i <= m; i++) {
for (int j_ = 0; j_ <= f; j_++) {
for (int k = 0; k <= j; k++) {
visited[i][j_][k] = 0;
}
}
}
// 从初始状态开始计算
printf("%d\n", dfs(m, f, j));
return 0;
}
关键解释和优化
1. memo[m][f][j]
和 visited[m][f][j]
:
-
memo[m][f][j]
:表示从状态(m, f, j)
出发,最终能喝完酒的所有合法路径数。 -
visited[m][f][j]
:用于标记是否已经计算过该状态,如果计算过,就直接返回memo[m][f][j]
,避免重复计算。
2. 递归过程:
-
遇到花:
dfs(m, f - 1, j - 1)
,遇到花,酒减 1,花减 1。 -
遇到店:
dfs(m - 1, f, j * 2)
,遇到店,酒加倍,店减 1。
3. 基本情况:
-
m == 0 && f == 1 && j == 1
:这是终止条件,表示最后一朵花喝光了酒,返回 1。 -
j > f
:如果酒的数量已经超过了花的数量,直接返回 0,因为不合法。
4. visited
数组:
-
只有在
visited[m][f][j]
为 0 时,递归才会继续,避免重复计算。否则,直接返回之前存储的memo[m][f][j]
。
我的问题和答案
1. visited[m][f][j]
是判断它是否为 0 吗?
答:是的,visited[m][f][j]
用来标记状态 (m, f, j)
是否已经访问过。初始化时为 0,表示未访问过;如果为 1,表示已经访问过,直接返回缓存的结果。
2. return memo[m][f][j];
是什么意思?
答:memo[m][f][j]
存储的是从状态 (m, f, j)
出发,到达目标状态的路径数。如果 visited[m][f][j] == 1
,就说明这个状态已经计算过了,直接返回结果而不再进行递归计算,从而避免重复计算。
3. 为什么同一个状态可以只算一次,不会漏掉其他的路径?
答:对于每个状态 (m, f, j)
,在这个状态下,剩下的路径数是唯一的,和之前的路径无关。所以无论我们是从哪条路径到达这个状态,后面的选择和结果都是一样的。我们通过 memo[m][f][j]
来存储每个状态的计算结果,后续直接复用,避免重复计算。
优化思路
-
空间优化:
visited
和memo
数组是三维的,如果状态空间非常大(如m
,f
,j
都达到上百),可以考虑优化空间。比如,只保留当前和上一层状态的结果,减少内存使用。 -
剪枝优化:比如判断酒的数量
j
如果大于花的数量f
时,可以立刻返回 0,这样避免了不合法的状态继续计算。 -
缓存优化:如果有大量重复状态,可以通过哈希表(例如
unordered_map
)替代数组来缓存计算结果,进一步优化内存和计算效率。