相信看过上面对于错排问题的简单的介绍,大家也都对它有了一些初步的了解,归结起来,就是考虑一个有n个元素的排列,若一个排列中所有的元素都不在自己原来的位置上,那么这样的排列就称为原排列的一个错排,n个元素的错排数记为D(n)。那么对于这样的排列D(n)有多少种呢?我们一步一步进行分析:
首先,对于D(n),有1~n这样n个元素错排,所以对于第一个元素①,它现在可能的位置有(n-1)个,倘若它在第k个元素的位置上,对于第k个元素而言,它所在的位置就有两种可能—第一种,它处在非第一个元素①位置上,所以对于接下来的排列就相当于是n-1个元素的错排,即D(n-1);第二种,它处在第一个元素①的位置上,所以在排列D(n)中有两个元素找到了位置,那么接下来的队列就相当于是n-2个元素的错排。因此,对于D(n)都有D(n)=(n-1)*(D(n-1)+D(n-2))【特殊的,D(1)=0,D(2)=1】。
得到这个递推式之后,我们进一步进行推导:为了运算的方便,我们设D(n)=n!*N(n),则有:
n!*N(n)=(n-1)*(n-2)!*N(n-2)+(n-1)*(n-1)!*N(n-1) ,两边同时除以(n-1)!,可得:n*N(n)=N(n-2)+(n-1)*N(n-1) ,移项:
N(n)-N(n-1)=(N(n-2)-N(n-1))/n = -(1/n)(N(n-1)-N(n-2)) ,所以N(n-1)-N(n-2)= -(1/(n-1))(N(n-2)-N(n-3)),··· ··· ,
直到N2-N(1)=1/2 ,将每一个式子相加易得:N(n)-N(1)=(1/2!-1/3!+1/4!- ··· ··· +((-1)^(n-1))/(n-1)!+((-1)^n)/n! )
由于N(1)=0,所以N(n)=(1/2!-1/3!+1/4!- ··· ··· +((-1)^(n-1))/(n-1)!+((-1)^n)/n! ) ,于是可以得到:
错排公式为D(n)=n!*(1/2!-1/3!+1/4!- 1/5!+ ··· ··· +((-1)^(n-1))/(n-1)!+((-1)^n)/n! )。
错排公式的原形为D(n) = n! (1/0! - 1/1! + 1/2! - 1/3! - ..... + (-1)^n/n!),当n很大时计算就很不方便。一个供参考的简化后的公式是D(n) = [n!/e+0.5] ,其中e是自然对数的底,[x]为x的整数部分。
证明:
由于1/e = e^(-1) = 1/0! - 1/1! + 1/2! - 1/3! - ..... + (-1)^n/n! + Rn(-1),
其中Rn(-1)是余项,等于(-1)^(n+1) * e^u / (n+1)!,且u∈(-1, 0).
所以,D(n) = n! * e^(-1) - (-1)^(n+1) * e^u / (n+1), u∈(-1, 0).
而|n! Rn| = |(-1)^(n+1) * e^u / (n+1)| = e^u / (n+1) ∈ (1/[e(n+1)], 1/(n+1)),可知即使在n=1时,该余项(的绝对值)也小于1/2。
因此,无论n! Rn是正是负,n! / e + 1/2的整数部分都一定与M(n)相同。
对于比较小的n,结果及简单解释是:
D(0) = 1(所有的元素都放回原位、没有摆错的情况)
D(1) = 0(只剩下一个元素,无论如何也不可能摆错)
D(2) = 1(两者互换位置)
D(3) = 2(ABC变成BCA或CAB)
D(4) = 9
D(5) = 44
D(6) = 265
D(7) = 1854
D(8) = 14833
D(9) = 133496
D(10) = 1334961
有N个数字(1~N),问全排列中,有多少个方案,使得a【i】==i的个数大于等于k.
#include
#include
using namespace std;
#define LL long long int
const int N = 1e5 + 7;
const int MOD = 1e9+7;
/**********************************/
LL qmod(LL a,LL b) {
LL res = 1ll;
while(b) {
if(b&1) res=res*a%MOD;
b>>=1,a=a*a%MOD;
}
return res;
}
LL dp[N];
LL fac[N],inv[N];
void init() {
fac[0]=1;
for(LL i=1; i=0; i--) inv[i]=(inv[i+1]*(i+1))%MOD;
}
LL C(int n,int m) {
return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
int main() {
init();
dp[0]=1;
dp[1]=0;
for(LL i=1; i<=10000; i++) {
dp[i]=(i-1)*(dp[i-1]+dp[i-2]);
dp[i]%=MOD;
}
int t;
int n,kk;
scanf("%d",&t);
while(t--) {
scanf("%d%d",&n,&kk);
LL output=0;
for(int k=kk; k<=n; k++) {
output+=C(n,k)*dp[n-k]%MOD;
output%=MOD;
}
printf("%I64d\n",output%MOD);
}
return 0;
}
下面用错排递推公式做几个几个例子;
例1;装错信封的问题(经典原型)HDU1465题
写n封信,全部装错信封,求有多少种全部装错的方式
#include
using namespace std;
int main() {
int n;
long long d[21] = {0,0,1};//将d[0],d[1],d[2]初始化
for(int i=3;i<=20;i++) {
d[i] = (i-1)*(d[i-1]+d[i-2]);
}
while(cin >> n) {
cout << d[n] <
例2:考新郎HUD2049
首先,给每位新娘打扮得几乎一模一样,并盖上大大的红盖头随机坐成一排;然后,让各位新郎寻找自己的新娘.每人只准找一个,并且不允许多人找一个.假设一共有N对新婚夫妇,其中有M个新郎找错了新娘,求发生这种情况一共有多少种可能.
阅读题目后我们不难发现,这道题的本质就是求解排列组合C(n,m)与错排m个元素D(m)的乘积,因此这道题的代码也十分简单,以下提供两种AC程序:
//#方法1:递推公式--D(n)=(n-1)*(D(n-1)+D(n-2)) [D(1)=0,D(2)=1]
#include//万能头文件,只有少部分oj支持
using namespace std;
int main() {
int ti,n,m;
long long f[21] = {0,0,1},t[21] = {1,1,2};
for(int i=3;i<=20;i++) {
f[i] = (i-1)*(f[i-1]+f[i-2]);
t[i] = t[i-1]*i;
}
cin >> ti;
while(ti--) {
cin >> n >> m;
long long a = t[n]/t[m]/t[n-m];
cout << a*f[m] <
/*#方法2:通项公式--D(n)=n!*(1/2!-1/3!+1/4!- 1/5!+ ··· ··· +((-1)^(n-1))/(n-1)!+((-1)^n)/n! )
这里可以根据题目做一下变形:
F(n,m)=C(n,m)*D(m)=n!*(1/2!-1/3!+1/4!- 1/5!+ ··· ··· +((-1)^(n-1))/(n-1)!+((-1)^n)/n! )/(n-m)!*/
#include
using namespace std;
int ti,n,m;
long long f[21] = {0,0,1},t[21] = {1,1,2};
long long sol() {
long long sum=0,a=t[n],b=t[n-m];
for(int i=2; i<=m; ++i) {
a/=i;
if(i%2==0)
sum+=a;
else
sum-=a;
}
return sum/b;
}
int main() {
for(int i=3; i<=20; i++) {
f[i] = (i-1)*(f[i-1]+f[i-2]);
t[i] = t[i-1]*i;
}
cin >> ti;
while(ti--) {
cin >> n >> m;
cout << sol() <