接力赛换两个人跑位就是另一种安排——「排好顺序」的方法数,每个位置依次减 1,乘起来就是 A(n,k)。
学校 4×100 米接力,4 名选手的棒次(谁跑第几棒)就是「顺序」:小明跑第 1 棒是一种安排,小明跑第 2 棒是另一种——换两个人的位置,就是新的排列。这就是「顺序有关系」。
从 4 人(甲乙丙丁)里选 3 人当班长、副班长、宣传委员:第 1 个位置(班长)4 种选法,第 2 个位置 3 种(班长不能再用),第 3 个位置 2 种 → 4 × 3 × 2 = 24 种。数学上把「从 n 个里选 k 个按顺序排好」叫做排列,方法数记作 A(n,k),公式 A(n,k) = n × (n−1) × … × (n−k+1)(一共 k 个因子)。
把 n 个全部排成一列叫全排列,数 = n × (n−1) × … × 2 × 1,记作 n!(n 的阶乘)。3 个人排队 = 3! = 3 × 2 × 1 = 6 种。
4 人(甲乙丙丁)选 3 人当班长、副班长、宣传委员。三个职位不一样:甲当班长、乙当副班长,和甲当副班长、乙当班长,是两种不同的安排——顺序有关系,所以用排列数 A(4,3)。
// 手推:一个位置一个位置填 第 1 个位置(班长):4 人选 1 → 4 种 第 2 个位置(副班长):剩 3 人选 1 → 3 种(班长不能再当) 第 3 个位置(宣传委员):剩 2 人选 1 → 2 种 → A(4,3) = 4 × 3 × 2 = 24 种 // 一般公式:A(n,k) = n × (n−1) × … × (n−k+1),一共 k 个因子 A(4,3) = 4 × 3 × 2 = 24 // 3 个因子,从 4 降到 2 A(5,3) = 5 × 4 × 3 = 60 // 3 个因子,从 5 降到 3 A(10,2) = 10 × 9 = 90 // 2 个因子 A(4,4) = 4 × 3 × 2 × 1 = 24 // 选 4 个排 4 列 = 全排列 4! // 两个约定:A(n,1) = n(只选 1 个排 1 列,就是选 1 个);A(n,n) = n!
3 个人(A、B、C)排成一排照相:第 1 位 3 种、第 2 位 2 种、第 3 位 1 种 → 3 × 2 × 1 = 6 种。把 3 个全部排好叫全排列,数写作 3!(读「3 的阶乘」)。动手枚举一遍,正好 6 种:
// 枚举 3 人全部 6 种站法(固定第 1 位,再排后面两位) A B C A C B B A C B C A C A B C B A // 阶乘速查表(常用值,建议记住前 5 个) 1! = 1 2! = 2 3! = 6 4! = 24 5! = 120 6! = 720 7! = 5040 // 规定:0! = 1(先记住,后面 A(n,0) = 1 会用到它) // 阶乘长得飞快:10! = 3,628,800;13! ≈ 62 亿, // 已经超过 C++ int 上限(2,147,483,647 ≈ 21 亿)→ 大数要用 long long
if 过滤枚举 3 人全部 6 种排队,理解「过滤保证每个人只出现一次」。思路:让 3 个位置都从 {A,B,C} 里独立选,一共 3×3×3 = 27 种组合;再用一个 if 把「同一个人占了两个位置」的 21 种过滤掉,剩下的 6 种恰好是 3! 。
// 枚举 A B C 三人全部 6 种排队 char p[3] = { 'A', 'B', 'C' }; int cnt = 0; for (int i = 0; i < 3; i++) // 第 1 位:3 人选 1 for (int j = 0; j < 3; j++) // 第 2 位:3 人选 1 for (int k = 0; k < 3; k++) { // 第 3 位:3 人选 1 // 三个人两两不同,才算合法排队 if (p[i] != p[j] && p[j] != p[k] && p[i] != p[k]) { cnt++; cout << cnt << ": " << p[i] << p[j] << p[k] << endl; } } // 输出恰好 6 行 = 3!: // 1: ABC 2: ACB 3: BAC // 4: BCA 5: CAB 6: CBA
p[i] != p[k],会输出哪种「有人重复」的排队?举个例子。③ 想枚举「3 人选 2 人排队」(A(3,2)),代码怎么改?p[i] != p[k] 后,第 1、3 位相同不再被拦,会输出 A B A(A 出现了两次)。③ 只留两层循环(i、j)+ if (p[i] != p[j]),输出 p[i]、p[j],共 A(3,2) = 3×2 = 6 种。
if 里必须检查所有两两组合:(i,j)、(j,k)、(i,k) 三个条件一个都不能少。只查相邻位置(i≠j、j≠k)会漏掉「隔位重复」的 A B A。// 计算 A(n,k):从 n 开始连乘 k 个「逐个减 1」的因子 int A(int n, int k) { int res = 1; for (int i = 0; i < k; i++) // 乘 k 个因子:n, n-1, ..., n-k+1 res *= (n - i); return res; } A(5,3) = 5 × 4 × 3 = 60 A(4,3) = 4 × 3 × 2 = 24 A(10,2) = 10 × 9 = 90 // 应用题:一排 3 个方格,用红/绿/蓝 3 种颜色涂, // 每格 1 色且 3 格颜色互不相同,有多少种涂法? // 第 1 格:3 种;第 2 格:2 种;第 3 格:1 种 → A(3,3) = 3! = 6 种 // 枚举:红绿蓝、红蓝绿、绿红蓝、绿蓝红、蓝红绿、蓝绿红
for (int i = 1; i <= k; i++) res *= (n - i); 会从 n−1 开始乘,整组因子差一位,算出来是 (n−1)×(n−2)×…——第一个因子必须是 n 本身,所以用 n - i(i 从 0 开始)。// 输入:n, k(1 ≤ k ≤ n ≤ 13,结果用 long long 防溢出) // 输出:① A(n,k) 的值 ② 若 n ≤ 3,把 n 个元素的全排列全列出来 // 示例:输入 4 3 → A(4,3) = 24 // 示例:输入 3 2 → A(3,2) = 6,并列出 3 的全排列 6 种 #include <iostream> using namespace std; int main() { int n, k; cin >> n >> k; long long res = 1; for (int i = 0; i < k; i++) // 连乘 k 个递减因子 res *= (n - i); cout << "A(" << n << "," << k << ") = " << res << endl; if (n <= 3) { // n 小就演示全排列 char p[] = { 'A', 'B', 'C' }; int cnt = 0; if (n == 1) { cout << "1: A" << endl; } else if (n == 2) { cout << "1: AB" << endl; cout << "2: BA" << endl; } else { // n == 3:三重循环 + 两两不等 for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) for (int m = 0; m < 3; m++) if (i != j && j != m && i != m) { cnt++; cout << cnt << ": " << p[i] << p[j] << p[m] << endl; } } } return 0; } // 动手验证:输入 5 3 → A(5,3) = 60(不枚举) // 输入 2 2 → A(2,2) = 2 + 1: AB / 2: BA