错排公式

  相信看过上面对于错排问题的简单的介绍,大家也都对它有了一些初步的了解,归结起来,就是考虑一个有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() <

你可能感兴趣的:(ACM,数论)