去年幫部門排週末值班,題目是「12 個人裡選 4 個」。我算出 495,隔壁工位的老周算出 11880。草稿拍在桌上互相驗了一下午,才發現問的壓根不是同一件事:我說的是「哪四個人」,他說的是「哪四個人各守哪個崗」。
這之後我又碰到好幾回同類爭執:抽獎的覆蓋率、比賽名單的搭配、四位數字鎖。栽多了,我給自己定了一條很笨但有用的判準,下面這些數都逐個算過。
順序算不算數,我做「交換測試」
把一個結果原樣寫下來,交換其中兩個成員的位置,只問一句:這還算不算新的一種?算,順序就算數,用排列;不算,用組合。聽著像廢話,做成動作後,絕大多數題目不用再猶豫。
| 場景 | 交換兩個位置之後 | 該用哪個 | 數量 |
|---|---|---|---|
| 12 人取 4 人,只決定誰上 | 還是那四個人,名單沒變 | 組合 | C(12,4)=495 |
| 12 人取 4 人,分守 4 個不同的崗 | 班次對不上人,值班表變了 | 排列 | A(12,4)=11880 |
| 52 張牌發 5 張,只看手裡是哪五張 | 還是那五張,先後不同不算新的 | 組合 | C(52,5)=2598960 |
| 4 位數字鎖,數字不許重複 | 1234 與 4321 是兩把鎖 | 排列 | A(10,4)=5040 |
| 4 位數字鎖,數字可重複 | 每位獨立,各有 10 種 | 兩個都不是 | 10×10×10×10=10000 |
最後一行是我每次都要先問的第二件事:可不可重複。兩個公式預設都不重複,一旦允許重複就既不是 A 也不是 C,而是每位獨立相乘。
兩個公式之間只差一個 r!
排列數 A(n,r) = n! / (n-r)!,組合數 C(n,r) = n! / (r!(n-r)!),差別只在分母多出的那個 r!,即 A(n,r) = C(n,r) × r!。這是一次現成的自檢:兩個數除一下,應該正好等於 r 的階乘,除不平就說明題意認錯了。
我核對過的幾組:
- n=12,r=4:C=495,A=11880,比值 24,正好是 4!
- n=9,r=4:C=126,A=3024,比值 24
- n=7,r=3:C=35,A=210,比值 6,正好是 3!
- n=10,r=3:C=120,A=720,比值 6
- n=6,r=2:C=15,A=30,比值 2
組合數還有兩條手算驗錯的路:對稱性 C(n,r)=C(n,n-r),如 C(10,3) 與 C(10,7) 都是 120,所以手算挑較小的 r,乘法步數少一半;帕斯卡遞推 C(n,r)=C(n-1,r-1)+C(n-1,r),如 C(8,3)=C(7,2)+C(7,3)=21+35=56。要列全部方案我不手敲,交給 排列計算器,它連公式帶過程一起列。
我真正算錯的三類
第一類就是「可重複」漏問。同一把四位鎖有三種理解、三個數:數字可重複是 10×10×10×10=10000;不許重複且順序算數是 A(10,4)=10×9×8×7=5040;不許重複、只問用了哪四個數字不計先後,是 C(10,4)=5040/(4×3×2×1)=210。題目只說「四位數字」時我吃過虧。
第二類是「至少」。5 男 7 女裡選 3 人,要求「至少有 1 名女生」。我一開始先定 1 名女生再隨便補 2 人:C(7,1)×C(11,2)=7×55=385,當答案交了。正確做法是整體減反面:C(12,3)-C(5,3)=220-10=210。多出的 175 不是筆誤:兩名女生的組被數兩遍,三名女生的組被數三遍——按女生人數拆開是 70+105+35=210,我的算法得到 1×70+2×105+3×35=385。這種重複計數不會報錯,只有換一種數法才看得見。
第三類是機率的分母。分母必須是完整玩法的組合數,不是其中一步:紅球 6/33 加藍球 1/16,總數 C(33,6)×16=1107568×16=17721088,只算紅球就少了 16 倍;前後兩區那種是 C(35,5)×C(12,2)=324632×66=21425712。尺度感:五張牌區分花色的組合 2598960 種,一天摸一種、按一年 365 天計要 7120.4 年;把四種花色視作等價只數牌型,才是 7462 種。
數字為什麼爆得這麼快
兩個公式都靠階乘,而階乘的增長完全不講道理。下面是我自己算的對照:
| 階乘 | 值 | 是否還在 2^53-1 之內 |
|---|---|---|
| 15! | 1307674368000 | 在 |
| 18! | 6402373705728000 | 在 |
| 19! | 121645100408832000 | 越線 |
| 20! | 2432902008176640000 | 越線 |
這裡的 2^53-1 是 9007199254740991,普通數值類型精確表示整數的上限。但越線不等於當場失真:我把 1! 到 25! 逐個大數對位比過,15! 到 22! 與 double 完全相同,第一次真丟位是 23!,精確值 25852016738884976640000,double 給 25852016738884978212864。它們越線還沒丟,是末尾因子 2 多、有效位壓得進 53 位,碰巧裝得下,不能指望;判準還是 2^53-1,9007199254740993 就會被存成 9007199254740992。所以 C(20,10)=184756 這題,double 算 20!/(10!×10!) 得到的仍是 184756,沒失真;我以前咬定「中間那步早就錯了」,錯在把「結果看著正常」當成「中間量精確」——中間量換成 23! 往上,商就跟著錯。C(100,50) 是 30 位數 100891344545564193334812497256,固定寬度整數裝不下,這類題得用支援大數的算法:組合計算器 寫的是基於 BigInt 算大組合數,階乘計算器 寫的是 0 到 1000 不溢出。
還有一件事我分得很清:要「一個數」和要「全部明細」是兩個量級。後者行數按階乘漲,8 個元素全排列 40320 行,10 個是 3628800 行,先壓垮的是頁面不是算術;上限是實現定的,不是數學限制。
Q:A(n,r) 一定比 C(n,r) 大嗎?
大或者相等,不會小,因為 A = C × r!。r=1 時兩者都等於 n,r=0 時都等於 1,這是確認公式沒寫反的兩個邊界。
Q:從 7 個人裡挑 3 個開會,和挑 3 個人分別負責三個不同崗位,差多少?
35 種和 210 種,差 6 倍,也就是 3!。同三個人換一下分工就算新方案,那 6 倍必須乘上。
Q:數字大到算不動了怎麼辦?
先問是不是真要精確值。比較數量級用約等就夠;要精確到個位就不能拿普通數值類型硬算,得換成支援大數的做法,或者先把公式約分再乘。
Q:交上去之前怎麼自查?
我固定查三樣:A 除以 C 是不是正好等於 r!;有沒有把「可重複」的題錯用 A 或 C;「至少」是不是改成了整體減反面。三樣都過才敢交。
回到開頭那張值班表。最後用的是「分崗位」口徑的 11880,每人對應一個班次,交換位置就是一份新表;要是討論「乾脆隨機湊人」,該說的是 495。誰對誰錯不重要,先說清交換之後算不算新的一種。