1. 程式人生 > >PAT 發郵件 (錯排問題) - 詳細題解

PAT 發郵件 (錯排問題) - 詳細題解

這是一個非常經典的數學問題, 錯排問題

組合學中有這樣一個問題:某人給五個朋友寫信,邀請他們來家中聚會。請柬和信封交由助手去處理。粗心的助手卻把請柬全裝錯了信封。請問:助手會有多少種裝錯的可能呢?
這個問題是是由當時有名的數學家約翰·伯努利(Johann Bernoulli,1667—1748)的兒子丹尼爾·伯努利(Danid Bernoulli,17OO一1782)提出來的。瑞士著名數學家尤拉按一般情況給出了一個遞推公式:
    用A、B、C……表示寫著n位友人名字的信封,a、b、c……表示n份相應的寫好的信紙。把錯裝的總數為記作D(n)。假設把a錯裝進B裡了,包含著這個錯誤的一切錯裝法分兩類:
(1)b裝入A裡,這時每種錯裝的其餘部分都與A、B、a、b無關,應有D(n-2)種錯裝法。    
(2)b裝入A、B之外的一個信封,這時的裝信工作實際是把(除a之外的)n-1份信紙b、c……裝入(除B以外的)n-1個信封A、C……,顯然這時裝錯的方法有D(n-1)種。
總之在a裝入B的錯誤之下,共有錯裝法D(n-2)+D(n-1)種。
a裝入C,裝入D……的n-2種錯誤之下,同樣都有D(n-1)+D(n-2)種錯裝法,因此D(n)=(n-1)[D(n-1)+D(n-2)]
  這是遞推公式,令n=1、2、3、4、5逐個推算就能解答蒙摩的問題。
    D(1)=0,D(2)=1,D(3)=2,D(4)=9,D(5)=44
答案是44種。

記著公式會用就好了, 有興趣的同學可以推演一下, 不算太難理解

//發郵件 錯排問題
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
#include <cmath>
using namespace std;
#define ms(x, n) memset(x,n,sizeof(x));
typedef  long long LL;
const LL maxn = 25;

LL cont[maxn]; //cont[i]表示把i個郵件全部裝錯的方案數
void init()
{
    cont[1] = 0, cont[2] = 1, cont[3] = 2;
    for(int i = 4; i <= maxn; i++)
        cont[i] = (i-1)*(cont[i-1]+cont[i-2]);
}
int main()
{
    int n;
    init();
    while(cin >> n){
        cout << cont[n] << endl;
    }
    return 0;
}