题意
n个人有n把魔杖, 求这n个人至少有k个人拿到属于自己的魔杖的排列方式种数
思路
组合数学 + 错排公式
完全错排公式 : D(n)=(n-1)*(D(n-1)+D(n-2))
题目意思就是n个数字里要求至少k个数字位置不变,其余进行错排的方案数,容易想到不变的数字从k枚举到n,每次取i(k <= i < n)个出来,对剩下的n-i个进行错排,即C(n, i) * d[n - i] (d[n - i]表示对n-i个数进行错排的方案数),然后将它们累加,但是这样做超时了。
考虑到n较大,k较小,可以反过来处理,总的方案数为n!,可以从总的方案数里减去不符合条件的情况,即不变的个数从0枚举到k-1
AC代码
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const ll mod = 1e9+7;
const int maxn = 1e4+5;
ll d[maxn];
ll A[maxn];
ll C[maxn][100+5];
void init(){
d[0] = 1, d[1] = 0, d[2] = 1;
for( int i = 3; i <= 1e4; i++ ) //错排公式
d[i] = (i-1)*((d[i-2] + d[i-1])%mod) % mod;
A[0] = 1, A[1] = 1;
for( int i = 2; i <= 1e4; i++ ) //阶乘
A[i] = A[i-1]*i % mod;
C[1][0] = 1, C[1][1] = 1;
for( int i = 2; i <= 1e4; i++ ){ //组合数
C[i][0] = 1;
for( int j = 1; j <= i && j <= 100; j++ )
C[i][j] = ( C[i-1][j]+C[i-1][j-1] ) % mod;
}
}
int main()
{
int T, n, k;
init();
scanf("%d",&T);
while(T--){
scanf("%d%d", &n, &k);
ll w = 0;
for( int i = 0; i < k; i++ )
w = ( w + C[n][i]*d[n-i]%mod ) % mod;
w = (A[n]-w+mod)%mod;
printf("%lld
",w);
}
return 0;
}