題意:n個巫師參加會議,每個人有一根屬於自己的魔杖。有一個巫師想打亂原本魔杖的排列順序,問至少有k個人拿對的打亂方法有多少種。
思路:
題目要求至少有K個人拿對那麼就是說拿對的人可以爲k,k+1,....n.例如n=5,k=3,那麼可以有3,4,5個人拿對。
解決:
n根魔杖的所有排列方式爲A(n,n),所以只需要用所有的減去1,2。。。k個人拿錯的情況就可以了。至於爲什麼不累加從k個人排錯到n,是因爲n較大會超時(錯了才知道)。
所以只需要累加1,2..k個人拿錯的情況就行了。列如k個人拿錯,首先需要從n個人中選出k個人去哪錯,即C(n,k),然後其他n-k個人排錯,即乘錯排數。所以k個人拿錯的的結果爲
C(n,k)*a[n-k].(0<=k<=n)
排列組合公式:
下面是代碼,其中求組合數用了費小馬定理把除法變成了相乘,優化了算法。
費小馬定理:假如p是質數,且gcd(a,p)=1,那麼 a(p-1)≡1(mod p),即:假如a是整數,p是質數,且a,p互質(即兩者只有一個公約數1),那麼a的(p-1)次方除以p的餘數恆等於1。因爲a*a^-1=1=a^(p-1)%p,所以a^-1=a^(p-2)%p.
#include<stdio.h>
#include<iostream>
#include<cstring>
using namespace std;
#define ll long long
#define mod 1000000007
ll a[10005],b[10005];
void init()
{
a[0] = 1;
a[1] = 0;
a[2] = 1;
b[1] = 1;
b[2] = 2;
for (int i = 3; i <= 10000; i ++)
{
a[i] = (((i - 1) % mod) * ((a[i - 2] + a[i - 1])) % mod) % mod;
b[i] = ((b[i - 1] % mod) * (i % mod)) % mod;
}
}
ll qpow(ll a, ll x) ///快速冪
{
ll res = 1;
while(x)
{
if (x & 1)
{
res = (res * a) %mod;
}
a = (a * a) % mod;
x >>= 1;
}
return res;
}
ll C(ll n, ll k) ///求組合數模版
{
if(n < k)
{
return 0;
}
if(k > n - k)
{
k = n - k;
}
ll a = 1, b = 1;
for(int i = 0; i < k; i++)
{
a = a * (n - i) % mod; ///n*(n-1)*(n-2).....*k
b = b * (i + 1) % mod; ///1*2*...*k
}
return a * qpow(b, mod - 2) % mod; ///費小馬定理
}
int main()
{
int n,m;
int t;
scanf("%d",&t);
init();
while(t--)
{
scanf("%d %d",&n,&m);
ll sum=b[n];
ll ans=0;
for(int i=0;i<m;i++)
{
ans=( (ans%mod) + ((C(n,i)*a[n-i])%mod) )%mod;
}
// cout<<ans<<endl;
printf("%lld\n",(mod +(sum%mod)-(ans%mod) )%mod);
}
return 0;
}