错排数

定义

考虑一个有 \(n\) 个元素的排列,若一个排列中所有的元素都不在自己原来的位置上,那么这样的排列就称为原排列的一个错排。 \(n\) 个元素的错排数记为 \(D_n\)

对于情况较少的排列,可以使用枚举法。

\(n=1\) 时,全排列只有一种,不是错排,\(D_1 = 0\)

\(n=2\) 时,全排列有两种,即 \(1、2\)\(2、1\),后者是错排,\(D_2=1\)

当n=3时,全排列有六种,其中只有 \(3、1、2\)\(2、3、1\) 是错排,\(D_3=2\)。用同样的方法可以知道 \(D_4=9\)

最小的几个错排数是:\(D_1 = 0,D_2 = 1,D_3=2,D_4 = 9,D_5 = 44,D_6 = 265,D_7 = 1854\)

递推式:

考虑对于一个数 \(n\),放在了第 \(i\) 个位置,那么 \(i\) 这个数就可以放在 \(n\) 位置,或者除了 \(i,n\) 以为的任意一个位置。

(1)\(i\) 放在了 \(n\) 位置,那么剩下的 \(n-2\) 个数的排列方法就是 \(D_{n-2}\)

(2)\(i\) 不放在 \(n\) 位置,那么 \(i\) 就有 \(n-2\) 个位置可以去,其余的所有元素也都有 \(n-2\) 个位置可以去,那么就成为了 \(n-1\) 个元素的错排问题,方案数为 \(D_{n-1}\)

而这样的 \(i\) 一共有 \(n-1\) 个,所以最终的递推式就是:

\(D_{n}=(n-1)\times (D_{n-1}+D_{n-2})\)

排列计数

求有多少种 \(1\)\(n\) 的排列 \(a\),满足序列恰好有 \(m\) 个位置 \(i\),使得 \(a_i = i\)

答案对 \(10^9 + 7\) 取模。

\(1 \leq n \leq 10^6\)\(0 \leq m \leq 10^6\)

思路:

排列中有 \(m\) 个位置在原位置上,那么也就是有 \(n-m\) 个数不在原位置上,这部分的方案数就是 \(D_{n-m}\),而选取这 \(m\) 个位置的方案数为 \(\binom{n}{m}\),所以最终的答案为:

\(Ans=\binom{n}{m} D_{n-m}\)

code:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int mod=1e9+7;
const int N=1e6+10;
int fac[N],infac[N],inv[N],n,m,T,d[N];
void init()
{
	fac[0]=infac[0]=inv[0]=fac[1]=infac[1]=inv[1]=1;d[1]=0;d[2]=1;
	for(int i=2;i<N-1;i++)
	{
		d[i+1]=1ll*i*(d[i]+d[i-1])%mod;
		fac[i]=1ll*i*fac[i-1]%mod;
		inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
		infac[i]=1ll*infac[i-1]*inv[i]%mod;
	}
}
int C(int n,int m){return 1ll*fac[n]*infac[m]%mod*infac[n-m]%mod;}
int main()
{
	init();
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m);
		if(n==m) puts("1");
		else printf("%d\n",(1ll*C(n,m)*d[n-m]%mod+mod)%mod);
	}
	return 0;
}
posted @ 2023-03-09 16:00  曙诚  阅读(541)  评论(0)    收藏  举报