【PAT】 1020 發郵件(錯排問題)詳解

題目描述

NowCoder每天要給很多人發郵件。有一天他發現發錯了郵件,把發給A的郵件發給了B,把發給B的郵件發給了A。於是他就思考,要給n個人發郵件,在每個人僅收到1封郵件的情況下,有多少種情況是所有人都收到了錯誤的郵件?
即沒有人收到屬於自己的郵件。

輸入描述:
輸入包含多組數據,每組數據包含一個正整數n(2≤n≤20)。


輸出描述:
對應每一組數據,輸出一個正整數,表示無人收到自己郵件的種數。

輸入例子:
2
3

輸出例子:
1
2

題解

首先先上結論, 我們將當給n個人發郵件時的錯排情況數設爲D(n)

可以通過分析得到D(n)的遞推式爲

D(n) = (n - 1) * (D(n - 1) + D(n - 2))

下面是遞推式的推導過程

遞推式的推導過程

轉載地址 https://blog.csdn.net/weixin_39773661/article/details/87859791

問題:現有10本書按照順序擺放,現要求重新排列,使得新的書的順序中每一本書都不在原來的位置,求有多少種排列方式?

這個問題推廣一下,就是錯排問題,是組合數學中的問題之一。考慮一個有n個元素的排列,若一個排列中所有的元素都不在自己原來的位置上,那麼這樣的排列就稱爲原排列的一個錯排。 n個元素的錯排數記爲D(n)。 研究一個排列錯排個數的問題,叫做錯排問題或稱爲更列問題

OK,現在詳細分析這個問題,我們要的最終結果就是書的編號與所在位置的編號不同,在這裏,我們把n本書的錯排操作數記爲D(n),那n-1本就是D(n-1),n-2本就是**D(n-2)**啦,下面,我們把放置問題分爲兩步(初始位置號與書的編號相同):

第一步:
我們取一本書,書的編號爲m,現在這本書就在我們手中,注意,按照題目要求,最開始的時候這本書的位置號也是m號,按照題目要求,我們現在放書時不能放回這個位置m了,而是要選擇其他位置,那麼有多少種選擇呢,想一下,總共有n本書,n個位置,現在我手裏這本書不能把它放到位置m,那麼剩下的n-1個位置我當然就是隨便扔啦,也就是n-1種扔法,好,現在,我選擇了位置k,我決定把手裏這本書放到位置k這裏,記住這個是位置編號k,那麼,我肯定要把原來這裏的編號爲k的書拿出來,再把這本編號爲n的書放進去嘍。所以,現在我們手裏的書的編號是k

第二步:
我們把手裏這本編號爲k的書本放到書架,注意,放的過程中我們又面臨兩種情況,可以想到,此時此刻現在書架上編號m的位置是空着的,所以我們可以選擇放在這個位置上,書的編號爲k,位置編號爲m,沒錯,滿足題意,這是第一種情況,還有一種就是我不選擇這個空着的位置m,我再重新選擇一個新的位置,我們稱之爲第二種情況,下面詳細分析

**第一種情況:我把這本編號爲k的書放到這個編號爲m的地址,那現在我們面前是什麼狀況呢,就是位置k和位置m的書交換位置,也就是位置號不等於書號,即滿足錯排,總共n個位置,我們只動了m和k這兩個位置,那麼剩下的n-2個位置還是紋絲不動,保持一一對應的關係呢,那麼對於剩下的這n-2本書的錯排操作,我們又回到了問題的起點,求n-2本的錯排操作數D(n-2),結合第一步,我們可以得到第一種情況總共有(n-1)*D(n-2)**種方法

第二種情況:我們不選擇這個空着的位置m啦,我們手持這本編號爲k的書,我們從除了位置m以及位置k的剩下的n-2個位置中選擇一個位置,OK,我們現在開始想,我手裏這本書不能放在這個位置m,嗯嗯,除了第一步我們放置的那本書m不用管了,我們還要搞手裏這本和剩下的n-2本,也就是n-1本,同時又要求手裏這本k還不能放到位置m,這是不是就相當於把手裏這本加上剩下的n-2本也就是n-1本書進行錯排呢,哇哇哇,想一想,錯排的定義,要求每本書都不能呆在某一個特定位置,是不是剛好符合呢qwq,所以,現在的爲題就到了求手裏這本和剩下的n-2本總共是n-1本書的錯排操作數,我們記爲D(n-1),結合第一步,我們得出這第二種情況共有**(n-1)*D(n-1)**種方法

好的,現在我們總結兩種情況,結果進行相加,就可以得到遞推公式啦
遞推公式爲:
D(n)=(n-1)*[D(n-1)+D(n-2)

題解代碼

因爲題目只要求 n <= 20 的情況, 所以可以先用數組算出前20的錯排數; 這裏數組的下標和n 一一對應

#include<iostream>
#include<cctype>
#include<algorithm>
#include<queue>
#include<stack>
#include<map>
using namespace std;


int main()
{
#ifdef ONLINE_JUDGE    
#else    
    freopen("1.txt", "r", stdin);    
#endif   
	
	long long int D[21] = {0,0,1,2};   //錯排數 數列   
	
	for (int i = 3; i < 21; i++) {
		D[i] = (i-1) * (D[i-1] + D[i-2]);
		//cout << D[i] << endl;
	}
	
	int num = 0;
	
	while (cin >> num) {
		cout << D[num] << endl;    //直接輸出對應下標的值即可
	}
}

類似題目

年會抽獎
https://www.nowcoder.com/pat/2/problem/275

1023 考新郎
https://www.nowcoder.com/pat/2/problem/279
前面加上一個組合數即可

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章