【题目描述】
LMZ有n个不同的基友,他每天晚上要选m个进行[河蟹],而且要求每天晚上的选择都不一样。那么LMZ能够持续多少个这样的夜晚呢?当然,LMZ的一年有
10007
10007
10007 天,所以他想知道答案
m
o
d
10007
mod \ 10007
mod 10007的值。
(
1
<
=
m
<
=
n
<
=
200
,
000
,
000
)
(1<=m<=n<=200,000,000)
(1<=m<=n<=200,000,000)
【输入】
第一行一个整数
t
t
t ,表示有t组数据。
(
t
<
=
200
)
(t<=200)
(t<=200)
接下来t行每行两个整数
n
,
m
,
n, m,
n,m, 如题意。
【输出】
T行,每行一个数,为
C
(
n
,
m
)
m
o
d
10007
C(n, m) \ mod \ 10007
C(n,m) mod 10007的答案。
【算法分析】
C
n
m
=
n
!
m
!
×
(
n
−
m
)
!
C_n^m=\frac {n!} {m!\times (n-m)!}
Cnm=m!×(n−m)!n!
通常需要对一个数
p
p
p 取模,可以先计算分子
n
!
m
o
d
p
n!\ mod \ p
n! mod p ,再计算分母
m
!
(
n
−
m
!
)
m
o
d
p
m!(n-m!)\ mod\ p
m!(n−m!) mod p 的逆元 ,乘起来得到
C
n
m
m
o
d
p
C_n^m\ mod \ p
Cnm mod p 的值。
在计算阶乘的过程中, k ! m o d p ( 0 ≤ k ≤ n ) k!\ mod\ p(0\le k \le n) k! mod p(0≤k≤n) 保存在数组 j c [ ] jc[\ ] jc[ ]中, k ! m o d p k!\ mod\ p k! mod p 的逆元保存在数组 j c _ i n v [ ] jc\_inv[\ ] jc_inv[ ]中,可以在 O ( n log n ) O(n\log n) O(nlogn) 的时间预处理,在 O ( 1 ) O(1) O(1) 的时间回答 y ≤ x ≤ n y\le x\le n y≤x≤n 的所有组合数:
C x y m o d p = ( j c [ x ] × j c _ i n v [ y ] × j c i n v [ x − y ] ) m o d p C_x^y\ mod\ p=(jc[x]\times jc\_inv[y]\times jc_inv[x-y]) \ mod\ p Cxy mod p=(jc[x]×jc_inv[y]×jcinv[x−y]) mod p
时间复杂度
O
(
n
log
n
)
O(n\log n)
O(nlogn) 。
【参考程序】
方法一:阶乘逆元,时间复杂度 O ( P log P ) O(P \log P) O(PlogP),用于P较小的时候,询问次数比较多的时候
#include<bits/stdc++.h>
using namespace std;
const int N=10010;
const int P=10007;
int jc[N],jc_inv[N];//jc[i]保存i!%p,jc_inv[i]保存i!%p的逆元
int KSM(int x,int k)
{
int ans=1;
while(k)
{
if(k&1) ans=(ans*x)%P;
k>>=1;
x=(x*x)%P;
}
return ans%P;
}
//组合数C(n,m)=n!/(m!*(n-m)!),除法取模需要求逆元
//看作n!*(m!的逆元)*(n-m)!的逆元再取模
int C(int n,int m)
{
return jc[n]*jc_inv[m]%P*jc_inv[n-m]%P;
}
int Lucas(int x,int y)
{
if(!y) return 1;
if(x<y) return 0;
if(x<P&&y<P) return C(x,y);
return (Lucas(x%P,y%P)*Lucas(x/P,y/P))%P;
}
int main()
{
jc[0]=jc_inv[0]=1; //特殊的C(i,0),设定jc_inv[0]=1
for(int i=1;i<10007;i++) //预处理
{
jc[i]=(jc[i-1]*i)%P;
jc_inv[i]=KSM(jc[i],P-2); //费马小定理求逆元
}
int n;
scanf("%d",&n);
int x,y;
while(n--)
{
scanf("%d%d",&x,&y);
cout<<Lucas(x,y)<<endl;
}
return 0;
}
方法二:直接求解,时间复杂度 O ( T M ) O(TM) O(TM) ,T次询问用于M较小的时候
#include<bits/stdc++.h>
using namespace std;
const int P=10007;
int KSM(int x,int k)
{
int ans=1;
while(k)
{
if(k&1) ans=(ans*x)%P;
k>>=1;
x=(x*x)%P;
}
return ans%P;
}
//组合数C(n,m)=n!/(m!*(n-m)!)=n!/(n-m)!取模乘以m!模P的逆元
//P是质数,可以使用费马小定理
int C(int n,int m)
{
int a=1,b=1;
for(int i=n-m+1;i<=n;i++) a=(a*i)%P; //m个数相乘
for(int i=2;i<=m;i++) b=(b*i)%P;
return (a*KSM(b,P-2))%P;
}
int Lucas(int x,int y)
{
if(!y) return 1;
if(x<y) return 0;
if(x<P&&y<P) return C(x,y);
return (Lucas(x%P,y%P)*Lucas(x/P,y/P))%P;
}
int main()
{
int n;
scanf("%d",&n);
int x,y;
while(n--)
{
scanf("%d%d",&x,&y);
cout<<Lucas(x,y)<<endl;
}
return 0;
}