错排问题:

定义:

给定 \(n\) 元素集合 \(X\) ,它的每一个元素都有一个特定的位置.

而现在要求求出集合 \(X\) 的排列中没有一个元素在它指定位置上的排列的数目.

这样的问题叫做错排问题

解决:

我们设 \(D[n]\) 表示 \(n\) 个元素的错排数

根据容斥原理,我们能得到以下推导:

\[D_n=n!(1-\frac{1}{1!}+\frac{1}{2!}+...+(-1)^n \frac{1}{n!} \]

还可以通过递推式得到:

我们记 \(n\) 封信错排数为 \(D_n\), 利用递推得到:

由于需要错排,第一封信可以装到除了第一个信封的任意位置,我们设装到第二个信封。那么现在有两种情况:

  1. 第二封信装到第一个信封,问题变为 \((n-2)\) 封信的错排问题,即 \(D_{n-2}\)

  2. 第二封信不装到第一封信,那么现在就是第 \(i\) 封不能装到第 \(i(i>2)\) 个,而第二封不能装到第 \(1\) 个信封,所以就是 \((n-1)\) 封信的错排问题,即 \(D_{n-1}\).

由于第三,四...封信都可以像第二封信一样,形成上面两种情况,因此则能得出递推式:

\[D_n=(n-1)(D_{n-1}+D_{n-2}) \]

两边同时除以 \(n!\) ,转化以下就可以得到上面式子。

例题:

P4071 [SDOI2016]排列计数

题意:

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

分析:

\(m\) 个位置适合,有 \(n\) 个数,可以看成 \(C_n^m\) 的组合。

剩下 \(n-m\) 个数,要求是错排,那么就是 \(D[n-m]\)

相乘即可。

注意判断一下特殊情况。

#include<bits/stdc++.h>
using namespace std;
#define int long long 

const int mod=1e9+7,N=1e6+5;
int T;
int n,m;
int inv[N],mul[N],D[N];
int qmi(int a,int b){
    int res=1;
    while(b){
        if(b&1) res=res*a%mod;
        b>>=1; a=a*a%mod;
    }
    return res;
}

void init(int n){
    mul[1]=1;
    for(int i=2;i<=n;i++) mul[i]=(mul[i-1]*i)%mod;
    inv[n]=qmi(mul[n],mod-2);
    for(int i=n-1;~i;i--) inv[i]=(inv[i+1]*(i+1))%mod;
    D[1]=0;D[2]=1;
    for(int i=3;i<=n;i++) D[i]=(i-1)*(D[i-1]+D[i-2])%mod;
}
int C(int n,int m){
    if(m>n) return -1;
    if(m==n) return 1;
    return mul[n]*inv[n-m]%mod*inv[m]%mod;
}

signed main(){
    cin>>T;
    init(1000000);
    while(T--){
        scanf("%lld%lld",&n,&m);
        if(n==m+1) puts("0");
        else if(m==n) puts("1");
        else if(m==0) printf("%lld\n",D[n]);
        else printf("%lld\n",C(n,m)*D[n-m]%mod);
    }
    system("pause");
    return 0;
}