错排问题,错位排列

1、问题: 十本不同的书放在书架上。现重新摆放,使每本书都不在原来放的位置。有几种摆法?
这个问题推广一下,就是错排问题,是组合数学中的问题之一。考虑一个有n个元素的排列,若一个排列中所有的元素都不在自己原来的位置上,那么这样的排列就称为原排列的一个错排。 n个元素的错排数记为D(n)。 研究一个排列错排个数的问题,叫做错排问题或称为更列问题。
错排问题最早被尼古拉·伯努利和欧拉研究,因此历史上也称为伯努利-欧拉的装错信封的问题。这个问题有许多具体的版本,如在写信时将n封信装到n个不同的信封里,有多少种全部装错信封的情况?又比如四人各写一张贺年卡互相赠送,有多少种赠送方法?自己写的贺年卡不能送给自己,所以也是典型的错排问题。
--------------------------------------------------------

2、简化:1到n的全排列,第i个数不是i的排列共有多少种?

其实这就是“错位排序”。

-------------------------------------------------------

3、推导:

用容斥原理也可以推出错排公式:
正整数1, 2, 3, ……, n的全排列有 n! 种,其中第k位是k的排列有 (n-1)! 种;当k分别取1, 2, 3, ……, n时,共有n*(n-1)!种排列是至少放对了一个的,由于所求的是错排的种数,所以应当减去这些排列;但是此时把同时有两个数不错排的排列多排除了一次,应补上;在补上时,把同时有三个数不错排的排列多补上了一次,应排除;……;继续这一过程,得到错排的排列种数为
D(n) = n! - n!/1! + n!/2! - n!/3! + … + (-1)^n*n!/n! = ∑(k=2~n) (-1)^k * n! / k!,
即D(n) = n! [1/0! - 1/1! + 1/2! - 1/3! + 1/4! + ... + (-1)^n/n!].
其中,∑表示连加符号,k=2~n是连加的范围;0! = 1,可以和1!相消。

参考文章:http://www.cnblogs.com/AndyJee/p/4636075.html


<think>好的,我现在需要帮用户解决关于问题的C++实现代码的问题。首先,我得回忆一下问题的定义。,也就是全错位排列,指的是每个元素都不在原来的位置上的排列。比如n个元素,每个元素都不在原来的位置,这样的排列数称为数,通常用D(n)表示。 用户明确要求用C++实现,所以我得考虑如何用C++编写计算数的代码。根据之前的引用,用户提供的参考中有提到递归方法,比如引用[4]中提到定义了一个derangement函数来计算数。不过,递归在n较大时可能会有性能问题,所以可能需要动态规划来优化。 首先,数的递推公式我记得是D(n) = (n-1)*(D(n-1) + D(n-2))。当n=1时,D(1)=0;n=2时,D(2)=1。这是递归的基础情况。但直接用递归的话,重复计算很多,效率低。所以动态规划会更高效,用数组存储中间结果。 接下来,我需要考虑代码结构。用户给出的引用[4]中的例子可能用了递归,但实际实现中动态规划更优。所以应该优先展示动态规划的方法。不过,为了全面性,也可以同时给出递归和动态规划两种实现,并说明各自的优缺点。 然后,代码的输入输出部分。用户可能需要输入一个n,然后输出对应的数。比如,用户输入3,输出2,因为三个元素的有两种:{2,3,1}和{3,1,2}。 还需要注意数据类型的范围。比如当n比较大的时候,数会非常大,超出int的范围,所以应该使用long long类型来存储结果。比如引用[1]中提到的问题,用户原本用int导致误,改用double后解决,但在这里可能更合适的是用更大的整数类型,如long long,而不是浮点数。 测试一下边界情况,比如n=0时,可能定义为1(因为0个元素有一种排列方式,即空排列,可以算作),但有些定义可能n>=1。需要明确处理n=0的情况,或者限制输入范围。 然后,代码的注释和可读性也很重要。用户可能需要示例代码清晰易懂,所以加上必要的注释,解释递推关系和步骤。 此外,用户的问题可能还涉及到生成具体的错位排列,而不仅仅是计算数量。比如引用[3]中提到输出全错位排列,并统计个数。但用户当前的问题可能只需要计算数量,所以是否需要包含生成排列的代码呢?根据用户的问题,他直接询问的是问题的C++实现代码,可能主要是计算数,但如果有生成排列的示例可能更好。不过生成所有排列可能比较复杂,特别是当n较大时,时间和空间开销都很大。所以可能先专注于计算数,如果有余力再提生成排列的方法。 综上,我需要编写一个C++程序,使用动态规划计算数,处理大数情况,并给出示例代码。同时可能需要解释递推公式,并说明优化方法。此外,考虑到用户可能遇到的常见误,比如数据类型的问题,需要强调使用long long类型。</think>### 问题的C++实现代码 问题(全错位排列)要求每个元素都不在原始位置上,其递推公式为: $$D(n) = (n-1) \times (D(n-1) + D(n-2))$$ 其中基础条件为 $D(1)=0$,$D(2)=1$[^3]。 #### 动态规划实现(推荐) ```cpp #include <iostream> using namespace std; long long derangement(int n) { if (n <= 1) return 0; long long dp[n+1]; dp[1] = 0; dp[2] = 1; for (int i = 3; i <= n; i++) { dp[i] = (i-1) * (dp[i-1] + dp[i-2]); } return dp[n]; } int main() { int n; cout << "请输入元素个数n: "; cin >> n; cout << "数D(" << n << ") = " << derangement(n) << endl; return 0; } ``` **代码说明:** 1. 使用`long long`类型避免数值溢出 2. 时间复杂度$O(n)$,空间复杂度$O(n)$ 3. 支持$n \geq 1$的输入[^4] #### 递归实现(仅限小规模) ```cpp long long derangement_recursive(int n) { if (n == 1) return 0; if (n == 2) return 1; return (n-1) * (derangement_recursive(n-1) + derangement_recursive(n-2)); } ``` **注意:** 递归方式存在重复计算,时间复杂度为$O(2^n)$,仅适用于$n \leq 20$的情况[^2]。 ### 测试案例 | 输入n | 输出D(n) | |-------|----------| | 3 | 2 | | 4 | 9 | | 5 | 44 | | 10 | 1334961 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值