刷题笔记-错排问题总结
一、什么是错排问题
举例:
- 十本不同的书放在书架,现重新摆放,使得每本书都不在原来的位置上,有几种摆法?
- 一个人给十个同学写信,但他把所有的信都装错了信封,问共有多少种错误的方式?
以上问题推广,就是错排问题。
一个n个元素的排列,若一个排列中所有的元素都不在自己原来的位置上,那么这样的一个排列就称为原排列的一个错排。而研究一个排列的错排个数的问题,就称为错排问题(或称为更列问题)。**
二、错排公式
错排问题是组合数学中的问题之一。考虑一个有n个元素的排列,若一个排列中所有的元素都不在自己原来的位置上,那么这样的排列就称为原排列的一个错排。 n个元素的错排数记为Dn。 研究一个排列错排个数的问题,叫做错排问题或称为更列问题。
最早研究错排问题的是尼古拉·伯努利和欧拉,因此历史上也称为伯努利-欧拉的装错信封的问题。这个问题有许多具体的版本,如在写信时将n封信装到n个不同的信封里,有多少种全部装错信封的情况?又比如四人各写一张贺年卡互相赠送,有多少种赠送方法?自己写的贺年卡不能送给自己,所以也是典型的错排问题。
例如有{\displaystyle n}封写好了的信,收件人不同,胡乱放入{\displaystyle n}个写了地址的信封中,寄出,求没有一个收件人收到他所应接收的信的概率。当{\displaystyle n=4},在4! = 24个排列之中,只有9个是错排:
BADC, BCDA, BDAC,
CADB, CDAB, CDBA,
DABC, DCAB, DCBA,
所以有关概率为9/24 = 37.5%
推导:
显然D1=0,D2=1。当n≥3时,不妨设n排在了第k位,其中k≠n,也就是1≤k≤n-1。那么我们现在考虑第n位的情况。
当k排在第n位时,除了n和k以外还有n-2个数,其错排数为Dn-2。
当k不排在第n位时,那么将第n位重新考虑成一个新的“第k位”,这时的包括k在内的剩下n-1个数的每一种错排,都等价于只有n-1个数时的错排(只是其中的第k位会换成第n位)。其错排数为Dn-1。
所以当n排在第k位时共有Dn-2+Dn-1种错排方法,又k有从1到n-1共n-1种取法,我们可以得到:
n个元素的错排数记为D(n)
D(1)=0; D(2)=1;
D n=(n-1)(D n-1+D n-2)
三、错排公式的应用
(1)年会抽奖没人中奖的概率
//得出公式n人都不获奖的概率h(n) = (n - 1) * (f(n - 1) + f(n - 2)) / (n!)
#include<stdio.h>
int main()
{
long long der[21] = { 0,0,1 }, fun[21] = { 0,1,2 };//fun用于计算阶乘
//der数组用于存储n=i时的错排数
int i, n;
for (i = 3; i < 21; i++)
{
der[i] = (i - 1) * (der[i - 2] + der[i - 1]);
fun[i] = i*fun[i - 1];
}
while (cin>>n))
{
printf("%.2f%c\n", 1.0*der[n] /fun[n] * 100, '%');
}
return 0;
}
(2)考新郎
题目大意:
有N对新婚夫妇,其中所有的新娘站成一列,都盖上了红布。然后让新郎去找新娘,每个新郎只能找一个新娘,而且不能一个对一个。问其中M个新郎找错新娘的情况有多少种。
思考过程:
这其实就是一个错排问题+排列组合问题
首先要从N个新郎当中找出M个找错的。即C(N,M)。其次是对这M组新人进行错排,为D(M)。而且两者之间是乘法原则
错排和排列组合递推公式:
由于是第一次写排列组合这块的内容,写一下如何用递推公式求 C(N,M) 和 D(M)。
(1)求C(N,M)。如果我们要从N个数当中抽出M个数,那么对于N个数当中的任何一个数来说,只有被抽到和没有被抽到两种情况。不妨设K,如果没有被抽到,则需要在剩下的 N - 1 个数当中抽 M 个。即C(N - 1,M)。如果K被抽到了,那么只需要在剩下的 N - 1 个数当中抽 M - 1 个。即C(N - 1, M - 1)
所以,C(N,M) = C(N - 1,M - 1) + C(N - 1,M)
(2)求D(N)。对于{1,2,3,……N} 这N个数,如果1在K的位置,K在1的位置,(由于K可以为剩下N - 1 个数当中任意一个,所以有N - 1 种选法)那么剩下的 N - 2 个数错排即可,为(N - 1)* D(N - 2)。如果K在1的位置,而1不在K的位置,那么把1当做K,相当于对N - 1 个数进行错排。为 (N - 1)* D(N - 1)
所以,D(N)= (N - 1)* (D(N - 1)+ D(N - 2))
/*
错排
*/
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
typedef long long ll;
using namespace std;
int C[22][22];
ll D[22];
void init()
{
for (int i = 0; i <=20; i++)
C[i][0] = 1;
C[1][1] = 1;
for (int i = 2; i <= 20; i++)
for (int j = 1; j <= i; j++)
C[i][j] = C[i - 1][j] + C[i - 1][j - 1];
D[1] = 0, D[2] = 1;
for (ll i = 3; i <= 20; i++)
D[i] = (i - 1) * (D[i - 1] + D[i - 2]);
}
int main ()
{
init();
int c, n ,m;
cin>>c;
while(c--)
{
scanf("%d%d", &n, &m);
ll sum;
if (m * 2 > n) sum = C[n][n - m];
else sum = C[n][m];
sum *= D[m];
cout<<sum<<endl;
}
return 0;
}
参考文章
【1】原文链接:https://blog.youkuaiyun.com/Ber_Bai/article/details/77112975
【2】原文链接:https://zhuanlan.zhihu.com/p/124217494
【3】原文链接:https://www.cnblogs.com/fuhaots2009/p/3458874.html