0%

「2018夏令营提高组」错排

题目链接

题意

在一个序列 AA 中,若 AiA_i 的值为 ii,则称 ii 为稳定的。

求恰好有 mm 个数稳定的长度为 nn 的排列个数。

T500000,n,m106T\leq500000,n,m\leq10^6

思路

一个有 nn 个元素的排列,若一个排列中所有的元素都不在自己原来的位置上,那么这样的排列就称为原排列的一个错排。

DnD_n 表示 nn 个元素错排方案数。

  1. 将第 11 个元素放在不为 11 的任意位置(记为 kk),有 (n1)(n-1) 种放置方法。
  2. 放置第 kk 个元素:
    1.把它放在位置 11,此时对于 11(n1)(n-1) 种放置方法,对于剩下的 (n2)(n-2) 个元素有 Dn2D_{n-2} 种放置方法;
    2.不把它放在位置 11,则第 11 个位置有 (n1)(n-1) 种放法,此时可以把第 kk 个元素看作第 11 个,对于剩下的 (n1)(n-1) 个元素,共有 Dn1D_{n-1} 种放置方法。

得到错排公式的递推式:

D1=0D2=1Dn=(n1)(Dn2+Dn1)\begin{aligned} D_1&=0\\ D_2&=1\\ D_n&=(n-1)\cdot(D_{n-2}+D_{n-1}) \end{aligned}

本题显然是求 CnmDnmC^m_n\cdot D_{n-m}

实现

预处理出 1n1-n 的阶乘及其逆元,可在 O(1)O(1) 时间内求出组合数。

利用错排公式的递推式预处理出 DnD_n

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include<cstdio>
using namespace std;
const int mod=1e9+7;
int t,n,m;
long long fac[1000001],inv[1000001],d[1000001];
inline long long pow(long long a,int tim)
{
long long res=1;
while(tim) {
if(tim&1) {
res=res*a%mod;
}
a=a*a%mod,tim>>=1;
}
return res;
}
int main()
{
freopen("permutation.in","r",stdin);
freopen("permutation.out","w",stdout);
fac[0]=1;
for(int i=1;i<=1000000;++i) {
fac[i]=fac[i-1]*i%mod;
inv[i]=pow(fac[i],mod-2);
}
d[1]=0,d[2]=1;
for(int i=3;i<=1000000;++i) {
d[i]=(i-1)*(d[i-2]+d[i-1])%mod;
}
scanf("%d",&t);
while(t--) {
scanf("%d%d",&n,&m);
printf("%lld\n",n==m?1ll:(m==0?d[n]:fac[n]*inv[m]%mod*inv[n-m]%mod*d[n-m]%mod));
}
return 0;
}