考虑一个有n个元素的排列,若一个排列中所有的元素都不在自己原来的位置上,那么这样的排列就称为原排列的一个错排。 n个元素的错排数记为D(n)
典型问题如在写信时将n封信装到n个不同的信封里,有多少种全部装错信封的情况?又比如四人各写一张贺年卡互相赠送,有多少种赠送方法?自己写的贺年卡不能送给自己。等等……
定义
在组合数学,错排是指没有元素出现在自己原本位置的排列。即是说存在没有不动点的双射
$:S->S, (i)!=i,任意1<=i<=n。(n)的值如下:(由n=1起:)
0, 1, 2, 9, 44, 265, 1854, 14833, 133496, 1334961, 14684570, 176214841, 2290792932, ...
例如有n封写好了的信,收件人不同,胡乱放入n个写了地址的信封中,寄出,求没有一个收件人收到他所应接收的信的概率。当n=4,在4! = 24个排列之中,只有9个是错排:
BADC, BCDA, BDAC,
CADB, CDAB, CDBA,
DABC, DCAB, DCBA,
所以有关概率为9/24 = 37.5%
递推公式
显然D(1)=0,D(2)=1。当n ≥ 3时,不妨设n排在了第k位,其中k≠n,也就是1 ≤ k ≤ n-1 。 那么我们现在考虑第n位的情况。
当k排在第n位时,除了n和k以外还有n-2个数,其错排数为D(n-2)。
当k不排在第n位时,那么将第n位重新考虑成一个新的“第k位”,这时的包括k在内的剩下n-1个数的每一种错排,都等价于只有n-1个数时的错排(只是其中的第k位会换成第n位)。其错排数为D(n-1)。
所以当n排在第k位时共有D(n-2)+D(n-1)种错排方法,又k有从1到n-1共n-1种取法,我们可以得到:
D(n)=(n-1)(D(n-1)+D(n-2))
至于通项公式的推导,比较复杂,详见:
http://zh.wikipedia.org/wiki/%E9%94%99%E6%8E%92%E9%97%AE%E9%A2%98
个人想法:当k排在第n位时,除了n和k以外还有n-2个数,其错排数为D(n-2)。当k不排在第n位时,假设k排在了第m位,则等价于去除k,n排在了第m位,所以为D(n-2)
const int p = 1000000007; long long d[110]; d[1] = 0; d[2] = 1; for (int i = 3; i <= 100; i ++) d[i] = ((d[i-1] + d[i-2]) % p) * (i-1) % p;
错排概率
求错排概率可以用 dp 的方法:例 zoj 1619
//f[n][m] = f[n-m][0] / m! #include <cstdio> #include <iostream> #include <cstring> #include <algorithm> using namespace std; double f[110][110]; int main() { // freopen("input.txt", "r", stdin); f[1][0] = 0; f[1][1] = 1; f[0][1] = 0; f[0][0] = 1; double sum, tp; for (int i = 2; i <= 100; i ++) { sum = 0.0; for (int j = 1; j <= i; j ++) { tp = f[i-j][0]; for (int k = 2; k <= j; k ++) tp /= k; f[i][j] = tp; sum += tp; } f[i][0] = 1 - sum; } int n, m; while (~scanf("%d %d", &n, &m)) { printf("%.8f ", f[n][m]); } return 0; }
HDU1465 不容易系列之一
const int maxn = 30; ll dp[maxn]; void init() { dp[1] = 0; dp[2] = 1; dp[3] = 2; for (int i = 4; i <= 20; i ++) { dp[i] = (i - 1) * (dp[i-1] + dp[i-2]); } } int main() { init(); int n; while (~scanf("%d", &n)) printf("%lld ", dp[n]); return 0; }