合影只看「谁在框里」,站哪都一样——顺序没关系的计数 = 排列数 ÷ k!。
甲乙丙三人合影:拍出来是「甲乙丙」还是「丙甲乙」?照片里还是同样三个人——换位置还是同一张照片。这种「只看谁在框里、不看顺序」的计数叫组合,数记作 C(n,k)。
怎么算?先按排列数:4 人选 3 人去值日(不分工),先「排」1、2、3 号位:A(4,3) = 4×3×2 = 24;可同一组 3 个人可以排出 3! = 6 种「站位」,它们其实是同一组。所以组数要把排列数除以 k!:C(4,3) = 24 ÷ 6 = 4 组——枚举验证:甲乙丙、甲乙丁、甲丙丁、乙丙丁,正好 4 组。
公式:C(n,k) = A(n,k) ÷ k!。「除以 k!」的直觉:同一组 k 个人有 k! 种站位,排列把它们全数了一遍,除以 k! 就只留「组」了。
4 人(甲乙丙丁)选 3 人值日,不分工——只要「谁值日」一样,就是同一组。顺序没关系,用组合数 C(4,3)。
怎么算?先排后除:先按排列算 A(4,3) = 4 × 3 × 2 = 24。但注意:同一组 3 个人(比如甲乙丙)可以排出 3! = 3 × 2 × 1 = 6 种「站位」,排列把 6 种站位全数了一遍。要数「组」,就得把重复的站位除掉:
// 手推 C(4,3):先排后除 C(4,3) = A(4,3) ÷ 3! = (4 × 3 × 2) ÷ (3 × 2 × 1) = 24 ÷ 6 = 4 // 一共 4 组 // 枚举 4 组,验证公式: 1: 甲乙丙 2: 甲乙丁 3: 甲丙丁 4: 乙丙丁 // 正好 4 组,和公式一致 ✓ // 一般公式:C(n,k) = A(n,k) ÷ k! // 「除以 k!」的直觉:同一组 k 个人有 k! 种站位, // 排列把每种站位都数了,除以 k! 就只剩「组」了。 C(4,2) = A(4,2) ÷ 2! = 12 ÷ 2 = 6 // 子任务 2 会枚举验证
4 人(甲乙丙丁)选 2 人一组。按固定顺序枚举(每对的第 1 个人必须排在第 2 个人前面):
// 枚举全部 C(4,2) 对——固定顺序,每对只出现一次: 甲在一对里:甲乙, 甲丙, 甲丁 // 3 对 乙在一对里(甲乙已数过):乙丙, 乙丁 // 2 对 丙在一对里:丙丁 // 1 对 → 3 + 2 + 1 = 6 对 = C(4,2) ✓ // 对照排列 A(4,2) = 12:12 种排法长这样—— 甲乙 乙甲 甲丙 丙甲 甲丁 丁甲 乙丙 丙乙 乙丁 丁乙 丙丁 丁丙 // 每一「对」恰好出现两次(两种顺序都有), // 12 ÷ 2 = 6。除以 k! 除掉的,正是这份重复。
同样是「4 人(甲乙丙丁)选 2 人去黑板」,两句话差别很大:
// 情境 A:2 人去黑板,不分工作(一起上去就行) // 「甲乙」和「乙甲」是同样两个人 → 顺序没关系 → 组合 C(4,2) = 12 ÷ 2 = 6 种 // 情境 B:2 人去黑板,一人写、一人擦 // 「甲写乙擦」≠「乙写甲擦」→ 顺序有关系 → 排列 A(4,2) = 4 × 3 = 12 种 // 判断口诀:读完题,问自己一句—— // 「把选出来的两个人换一下位置,算同一种还是不同种?」 // 同一种 → 组合 C(n,k);不同种 → 排列 A(n,k) // 有分工(班长/副班长、写/擦、先手/后手)→ 一般都有顺序
5 人(甲乙丙丁戊)两两握手,每对人只握一次。一共握多少次?
// 算法一:按人数,再消重复 每人和其余 4 人各握一次:5 × 4 = 20 // 但「甲和乙握」与「乙和甲握」是同一次握手, // 每次握手被两个人各数了一遍 → 20 ÷ 2 = 10 次 // 算法二:一次握手 = 选出「一对人」→ 就是 C(5,2) C(5,2) = A(5,2) ÷ 2! = (5 × 4) ÷ 2 = 10 次 // 两种算法一致 ✓ // 一般:n 人两两握手总次数 = C(n,2) = n × (n − 1) ÷ 2 n = 4: 4 × 3 ÷ 2 = 6 // 和子任务 2 的 6 对对上号 n = 6: 6 × 5 ÷ 2 = 15 n = 10: 10 × 9 ÷ 2 = 45
// 输入:n, k(1 ≤ k ≤ n ≤ 13,结果用 long long 防溢出) // 输出:① A(n,k) 和 C(n,k) = A(n,k) ÷ k!(展示过程) // ② n 人两两握手总次数 = C(n,2) = n × (n − 1) ÷ 2 // ③ 若 n == 4 且 k == 2,枚举全部 C(4,2) 对(A B C D 代表 甲乙丙丁) // 示例:输入 4 2 → A(4,2) = 12, C(4,2) = 6,列出 6 对,握手 6 次 #include <iostream> using namespace std; long long A(int n, int k) { // 排列:k 个递减因子连乘 long long res = 1; for (int i = 0; i < k; i++) res *= (n - i); return res; } long long fact(int x) { // 阶乘:x! = 1 × 2 × … × x long long res = 1; for (int i = 2; i <= x; i++) res *= i; return res; } long long C(int n, int k) { // 组合 = 排列 ÷ k!(整数除法) return A(n, k) / fact(k); } int main() { int n, k; cin >> n >> k; cout << "A(" << n << "," << k << ") = " << A(n, k) << endl; cout << "C(" << n << "," << k << ") = " << A(n, k) << " ÷ " << fact(k) << " = " << C(n, k) << endl; cout << n << " 人两两握手 = C(" << n << ",2) = " << C(n, 2) << " 次" << endl; if (n == 4 && k == 2) { // 小例子:枚举全部对 char p[] = { 'A', 'B', 'C', 'D' }; int cnt = 0; for (int i = 0; i < 4; i++) for (int j = i + 1; j < 4; j++) { // j 从 i+1 起:固定顺序,不重不漏 cnt++; cout << cnt << ": " << p[i] << p[j] << endl; } } return 0; } // 动手验证:输入 5 2 → A(5,2) = 20, C(5,2) = 10, 握手 10 次 // 输入 6 3 → A(6,3) = 120, C(6,3) = 20, 握手 15 次