题目描述

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))

下面是递推式的推导过程

递推式的推导过程

问题:现有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;    //直接输出对应下标的值即可
	}
}