传送门 组合数学简单题。
A n s = ( n m ) ∗ 1 Ans=\binom {n} {m}*1 Ans=(mn)∗1~ ( n − m ) (n-m) (n−m)的错排数。 前面的直接线性筛逆元求。 后面的错排数递推式本蒟蒻竟然推出来了。 首先说说为什么 A n s = ( n m ) ∗ 1 Ans=\binom {n} {m}*1 Ans=(mn)∗1~ n n n- m m m的错排数。 考虑首先选出 m m m个排列正确的数有 ( n m ) \binom {n} {m} (mn)种选法。 然后剩下的 n − m n-m n−m个数因为有严格的大小关系相当于只需要保证每个数与其下标不相同。 那么我们把这 n − m n-m n−m个数提出来。 它们的错排数跟 1 1 1~ n n n- m m m的错排数是相同的。 因此就是是这样了。 所以错排数怎么推呢? 假设已经求出了 1 , 1 1,1 1,1~ 2 , 1 2,1 2,1 ~ 3 3 3 … 1 1 1 ~ n n n- 1 1 1的错排数,要求 1 1 1~ n n n的错排数。 我们设 1 1 1~ i i i的错排数为 f [ i ] f[i] f[i]。 考虑现在在某个排列 1 1 1~ i − 1 i-1 i−1中加入 i i i ( i ≥ 2 ) (i \geq 2) (i≥2)。 那么有两种情况。
已有的排列中排列正确的数个数为0,那么只用从原排列中随便选个数放到第 i i i个位置,然后拿 i i i去填空就行了,方案数为 ( i − 1 ) ∗ f [ i − 1 ] (i-1)*f[i-1] (i−1)∗f[i−1]。已有的排列中排列正确的数个数为1,那么把这个数挪到第 i i i个位置,然后用 i i i去填空就行了,由于 i − 1 i-1 i−1个数都有可能成为那个排列正确的数,而且对于剩下的 i − 2 i-2 i−2个数都是错排的,因此方案数为 ( i − 1 ) ∗ f [ i − 2 ] (i-1)*f[i-2] (i−1)∗f[i−2] => f [ i ] = ( i − 1 ) ∗ ( f [ i − 1 ] + f [ i − 2 ] ) f[i]=(i-1)*(f[i-1]+f[i-2]) f[i]=(i−1)∗(f[i−1]+f[i−2])代码:
#include<bits/stdc++.h> using namespace std; inline int read(){ int ans=0; char ch=getchar(); while(!isdigit(ch))ch=getchar(); while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar(); return ans; } const int mod=1e9+7,N=1e6+5; int T,n,m,f[N],ifac[N],fac[N]; int main(){ T=read(); f[0]=1,f[1]=0,fac[0]=fac[1]=ifac[1]=ifac[0]=1; for(int i=2;i<=N-5;++i)fac[i]=1ll*fac[i-1]*i%mod,ifac[i]=1ll*(mod-mod/i)*ifac[mod%i]%mod,f[i]=1ll*(f[i-1]+f[i-2])*(i-1)%mod; for(int i=2;i<=N-5;++i)ifac[i]=1ll*ifac[i]*ifac[i-1]%mod; while(T--)n=read(),m=read(),printf("%d\n",1ll*fac[n]*ifac[m]%mod*ifac[n-m]%mod*f[n-m]%mod); return 0; }