组合数学:P4071 排列计数
求有多少种 到 的排列 ,满足序列恰好有 个位置 ,使得 。
答案对 取模。
考虑转化问题。
恰好有 个位置有 ,就是在 个数中选出 个数的无序组合,即 。
除去这 个数,还有 个位置是满足任意 的,即错排的。
对于错排问题。设 为长度为 的错排序列个数。 假设考虑到第 个信封,初始时暂时把第 封信放在第 个信封中,然后考虑两种情况的递推:
- 前面 个信封全部装错;
- 前面 个信封有一个没有装错其余全部装错。 对于第一种情况,前面 个信封全部装错:因为前面 个已经全部装错了,所以第 封只需要与前面任一一个位置交换即可,总共有 种情况。
对于第二种情况,前面 个信封有一个没有装错其余全部装错:考虑这种情况的目的在于,若 n-1 个信封中如果有一个没装错,那么把那个没装错的与 交换,即可得到一个全错位排列情况。就可以得到:
选自 oi-wiki
两种情况是满足乘法原理的。同时,可以利用乘法逆元求组合数。
关于乘法逆元:
因为同余不满足同除性,根据费马小定理,若 为素数,,则 。
再用快速幂就好啦。
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6+10,Mod = 1e9+7;
int f[N],fac[N];
int T;
int mi(int x,int k) {
if(!k) return 1;
int p=mi(x,k>>1);
return (p*p%Mod)*(k&1?x:1)%Mod;
}
int ny(int x) {return mi(x,Mod-2);}
int C(int n,int m) {
return (fac[n]*ny(fac[n-m])%Mod)*ny(fac[m])%Mod;
}
signed main() {
f[0]=1;
//这里是因为当 n=m 时,不用考虑错排情况
f[2]=1;fac[0]=1,fac[1]=1,fac[2]=2;
for(int i=3;i<N;i++) {
f[i]=(i-1LL)*(f[i-1]+f[i-2])%Mod;
fac[i]=fac[i-1]*i%Mod;
}
ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
cin>>T;
while(T--) {
int n,m;
cin>>n>>m;
cout<<(C(n,m)%Mod)*f[n-m]%Mod<<"\n";
}
return 0;
}