BISHI90 【模板】记忆化搜索¶
中等通过率 26.12%python3样例通过牛客 AC
讲解章节:记忆化搜索与剪枝
一句话
给定分段递归式 f(a,b,c),多组询问 f(a,b,c) mod 1e9+7。
解题思路¶
这题考什么¶
记忆化搜索 / 递推。递归式是:
a<=0 或 b<=0 或 c<=0 -> 1
a < b 且 b < c -> f(a,b,c-1) + f(a,b-1,c-1) - f(a,b-1,c)
其它 -> f(a-1,b,c) + f(a-1,b-1,c)
+ f(a-1,b,c-1) - f(a-1,b-1,c-1)
朴素递归是指数级(每次分裂成 3~4 个子问题、深度上百),必须记忆化。 由于 1 <= a,b,c <= 100,状态总数只有 101^3 ≈ 1.03e6, 干脆离线一次性把整张表递推出来,之后每次询问 O(1) 查表。
递推顺序:所有转移的下标都不增(且至少有一维严格减小), 按 a 升序 -> b 升序 -> c 升序扫描时,用到的 f(a-1, , ):上一层 a,已算完; f(a, b-1, *):同层前一个 b,已算完; f(a, b, c-1):同行前一个 c,已算完。 所以一遍三重循环就够,不需要真的递归(也就绕开了递归深度问题)。 记忆化与递推的关系见 62-记忆化搜索与剪枝。
数据规模与复杂度¶
表大小 101^3 ≈ 1.03e6,T <= 1e3 次询问 O(1)。
Python 的坑(本题必看)¶
- 1e6 次 Python 层循环 + 取模在 2 秒限制下相当吃紧。这里做了两处优化:
- 「其它情况」分支(a >= b,或者 c <= b)的转移只依赖上一层 a-1, 各个 c 之间互不依赖,于是可以用 zip + 列表推导整行批量算出来, 把内层循环压到 C 层;
- 只有「a < b 且 c > b」这一段才有 c 方向的串行依赖,必须逐个算, 但它只占一行的尾部。
- 取模不要每一项都取,攒完一个表达式再 % MOD 一次即可(Python 大整数 不会溢出,少一次取模就快一点);
- 减法后可能为负,最后统一 % MOD 会自动转正(Python 的 % 结果非负);
- lru_cache + 递归写法在 100^3 状态下既慢又会撞递归深度(深度可达 300), 所以这里选自底向上递推,完全不用递归。
样例复核¶
f(1,1,1):a<b 不成立 -> 分支三 = f(0,1,1)+f(0,0,1)+f(0,1,0)-f(0,0,0)
f(2,2,2) = f(1,2,2)+f(1,1,2)+f(1,2,1)-f(1,1,1) = 2+2+2-2 = 4 ✓
参考实现¶
[:octicons-arrow-left-16: BISHI89](BISHI89.md) [BISHI91 :octicons-arrow-right-16:](BISHI91.md)