递推
先看一个经典的“爬楼梯问题”,假设正在爬一个 \(n\) 阶的楼梯,每次可以爬 \(1\) 阶或 \(2\) 阶。请问,总共有多少种不同的方法可以爬到楼顶?
例如:
- \(1\) 阶楼梯:只有一种方法(爬 \(1\) 阶)
- \(2\) 阶楼梯:有 \(2\) 种方法(爬 \(1+1\) 阶,或直接爬 \(2\) 阶)
- \(3\) 阶楼梯:有 \(3\) 种方法(\(1+1+1, 1+2, 2+1\))
- \(\dots\)
直接去枚举各种方案缺乏条理,反过来思考:第 \(n\) 阶怎么到的?
- 思考最后一步动作
- 要想到达第 \(n\) 阶,最后一步必然只有两种可能
- 从第 \(n-1\) 阶爬 \(1\) 阶上来。
- 从第 \(n-2\) 阶爬 \(2\) 阶上来。
- 要想到达第 \(n\) 阶,最后一步必然只有两种可能
- 建立递推关系
- 既然到达第 \(n\) 阶的方法,只能由上述两种情况构成,那么:\(到达第n阶的总方法数 = 到达第n-1阶的总方法数 + 到达第n-2阶的总方法数\)。
- 如果用 \(f_n\) 表示爬到第 \(n\) 阶的方法数,那么就得到了一个关键的递推关系:\(f_n = f_{n-1}+f_{n-2}\)。
这意味着想知道爬 \(n\) 阶楼梯的方法数,只需要知道爬 \(n-1\) 阶和 \(n-2\) 阶的方法数,然后把它们加起来就行了。
把基本情况(\(f_1 = 1, f_2 = 2\))和递推关系结合起来,就得到了一个完整的递推算法。
选择题:小明想通过走楼梯来锻炼身体,假设从第 1 层走到第 2 层消耗 10 卡热量,接着从第 2 层走到第 3 层消耗 20 卡热量,再从第 3 层走到第 4 层消耗 30 卡热量,依此类推,从第 k 层走到第 k+1 层消耗 10k 卡热量(\(k \gt 1\))。如果小明想从第 1 层开始,通过连续向上爬楼梯消耗 1000 卡热量,至少要爬到第几层楼?
- A. 14
- B. 16
- C. 15
- D. 13
答案
C。
假设小明最终爬到了第 N 层,这个过程包括了从 1→2, 2→3, ..., 直到 (N-1)→N 的所有攀爬。总共消耗的热量 C 是所有这些攀爬消耗热量的总和 C = (10×1) + (10×2) + (10×3) + ... + (10×(N-1))。
可以将公因数 10 提取出来:C = 10 × (1 + 2 + 3 + ... + (N-1)),括号内是一个等差数列求和。根据等差数列求和公式 1+2+...+m = m(m+1)/2,其中 m = N-1,那么 C = 10 × [ (N-1) × ((N-1)+1) / 2 ] = 10 × [ (N-1) × N / 2 ] = 5 × N × (N-1)。
小明想消耗至少 1000 卡热量,所以需要找到满足下面不等式的最小整数 N:5 × N × (N-1) ≥ 1000,两边同时除以 5,得到 N × (N-1) ≥ 200,解得 N = 15。
选择题:已知 \(f(1)=1\),且对于 \(n \ge 2\) 有 \(f(n)=f(n-1)+f(\lfloor n/2 \rfloor)\),则 \(f(4)\) 的值为?
- A. 4
- B. 5
- C. 6
- D. 7
答案
B。这是一个递推问题,可以按照给定的公式一步步计算出结果。
\(f(2) = f(1) + f(1) = 1 + 1 = 2\)
\(f(3) = f(2) + f(1) = 2 + 1 = 3\)
\(f(4) = f(3) + f(2) = 3 + 2 = 5\)
选择题:对于一个整数 \(n\),定义 \(f(n)\) 为 \(n\) 的各位数字之和,问使 \(f(f(x))=10\) 的最小自然数 \(x\) 是多少?
- A. 29
- B. 199
- C. 299
- D. 399
答案
B。可以把这个问题分成两步。
第一步:找到满足 \(f(y)=10\) 的最小自然数 \(y\)
这里的 \(y\) 实际上就是 \(f(x)\)。要找一个数 \(y\),使得它的各位数字之和为 \(10\)。为了让 \(y\) 最小,应该遵循两个原则:
- 位数尽可能少。
- 高位(左边的数字)尽可能小。
一位数不可能加起来等于 \(10\),两位数可以。要使两位数最小,十位应该最小。十位最小是 \(1\),那么个位就是 \(10-1=9\)。因此,满足 \(f(y)=10\) 的最小自然数 \(y\) 是 \(19\)。
第二步:找到满足 \(f(x)=19\) 的最小自然数 \(x\)
现在知道了 \(f(x)\) 的值是 \(19\),需要找到一个数 \(x\),使得它的各位数字之和为 \(19\)。同样,为了让 \(x\) 最小,应该:
- 位数尽可能少。
- 高位(左边的数字)尽可能小。
一位数不可能加起来等于 \(19\),两位数的最大数字之和是 \(9+9=18\),不够 \(19\)。所以 \(x\) 至少是三位数,要使三位数 \(x\) 最小,它的百位数应该最小。同理可得,这个数是 \(199\)。
选择题:有 8 个苹果从左到右排成一排,你要从中挑选至少一个苹果,并且不能同时挑选相邻的两个苹果,一共有多少种方案?
- A. 36
- B. 48
- C. 54
- D. 64
答案
C。
定义 \(f_n\) 为从 \(n\) 个苹果中挑选,允许一个都不选,且不挑选相邻苹果的方案数。先包含“一个都不选”的方案,这样可以使递推关系更简洁,最后再减去这个方案。
对于第 \(n\) 个苹果(最右边的一个),有两种选择:
- 不选第 \(n\) 个苹果:如果不选它,那么问题就变成了“从剩下的 \(n-1\) 个苹果中挑选”,方案数就是 \(f_{n-1}\)。
- 选第 \(n\) 个苹果:如果选了它,根据规则,就不能选第 \(n-1\) 个苹果。问题就变成了“从前 \(n-2\) 个苹果中挑选”,方案数就是 \(f_{n-2}\)。
因为这两个情况是互斥的,所以总方案数是两者之和:\(f_n = f_{n-1} + f_{n-2}\)。
这正是斐波那契数列的递推公式。
对于 \(f_0\),有 0 个苹果,只有一种方案,就是“一个都不选”,所以 \(f_0 = 1\);对于 \(f_1\),有 1 个苹果,有两种方案,“选”或“不选”,所以 \(f_1 = 2\)。
根据递推式可以算出 \(f_8 = 55\),这里包含了“一个苹果都不选”这种情况。题目要求“至少挑选一个苹果”,因此,需要从总方案数中减去“一个都不选”的那 1 种方案。
例题:P2789 直线交点数
在二维平面上有 \(N \ (1 \le N \le 25)\) 条直线,且保证不存在三线共点的情况,求出这 \(N\) 条直线在平面上可能构成的交点总数有多少种不同的可能值。
若 \(N\) 条直线互不平行且无三线共点,则交点数达到最大值 \(\dfrac{N(N-1)}{2}\)。然而,当部分直线相互平行时,交点总数会减少。
考虑现在一共 \(i\) 条直线,将其分为两部分:一部分由 \(j \ (1 \le j \le i)\) 条互相平行的直线组成,另一部分由剩余的 \(i-j\) 条直线组成。这 \(j\) 条直线之间彼此平行,相互不产生任何交点。这 \(j\) 条直线中的每一条,都会与剩余的 \(i-j\) 条直线中的每一条相交,且交点互不重合(因为不存在三线共点),因此,这部分会额外贡献 \(j \times (i-j)\) 个交点。因此,如果 \(i-j\) 条直线可以构成 \(k\) 个交点,那么加入了这 \(j\) 条平行线后,\(i\) 条直线就能构成 \(k + j \times (i-j)\) 个交点。
可以定义 \(f_{x,y}\) 表示 \(x\) 条直线是否能够凑出 \(y\) 个交点,根据上述分析进行递推,初始条件为 \(0\) 条直线凑出 \(0\) 个交点。
参考代码
#include <iostream>
using namespace std;
const int N = 30;
// f[x][y] 表示 x 条直线是否可以构成 y 个交点
bool f[N][N * N];
int main()
{
int n; cin >> n;
// 基础边界:0 条直线对应 0 个交点
f[0][0] = true;
// 递推计算 1 到 n 条直线的交点情况
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
// i - j 条直线最多能产生的交点数
int b = (i - j) * (i - j - 1) / 2;
for (int k = 0; k <= b; k++) {
if (f[i - j][k]) {
f[i][k + j * (i - j)] = true;
}
}
}
}
// 统计 n 条直线所有可能的交点种数
int ans = 0, b = n * (n - 1) / 2;
for (int i = 0; i <= b; i++) {
if (f[n][i]) {
ans++;
}
}
cout << ans << "\n";
return 0;
}
外层循环 \(i\) 遍历 \(i \in [1,n]\),共执行 \(n\) 次。中层循环 \(j \in [1,i]\),平均执行 \(\frac{n}{2}\) 次。内层循环遍历 \(k \in [0, \frac{(i-j)^2}{2}]\),上限不超过 \(\frac{n^2}{2}\),总时间复杂度上限为 \(O(n^4)\)。

浙公网安备 33010602011771号