今天遇到一道组合数取模的题,毫无疑问爆零了,我出题人真是个sb,连Lucas定理都不知道QAQ
Lucas定理: 求解C(n,m)%p , n 和 m 是 非负整数 , p是素数
结论1:Lucas(n,m,p) = C(n%p,m%p)* Lucas(n/p,m/p,p)
结论2. 把n写成p进制a[n]a[n-1]a[n-2]...a[0],把m写成p进制b[n]b[n-1]b[n-2]...b[0],则C(n,m)与C(a[n],b[n])*C(a[n-1],b[n-1])*C(a[n-2],b[-2])*....*C(a[0],b[0])模p同余。
*注:n,m不能大于10^5,不大于情况下用逆元的方法可以解决,如果大了就不能解决。
/*
* Created on: 2017-06-02
* Author: zjq
* 求C(n,m)%p, p为素数
* Lucas定理: Lucas(n,m,p)=c(n%p,m%p)*Lucas(n/p,m/p,p);
*/
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long long
LL fac[20005];
void init(LL p)
{
fac[0] =1;
for(LL i =1; i <= p; i++)
fac[i] = fac[i-1]*i % p;
}
LL exp_mod(LL a, LL b, LL p)
{
LL tmp = a % p, ans =1;
while(b)
{
if(b & 1) ans = ans * tmp % p;
tmp = tmp*tmp % p;
b >>=1;
}
return ans;
}
LL C(LL n, LL m, LL p)
{
if(m > n)
return 0;
return fac[n]*exp_mod(fac[m]*fac[n-m], p-2, p) % p;//逆元
}
LL Lucas(LL n, LL m, LL p)
{
if(m ==0)
return 1;
return (C(n%p, m%p, p)*Lucas(n/p, m/p, p))%p;
}
int main()
{
freopen("comb.in","r",stdin);
freopen("comb.out","w",stdout);
LL n,m,p;
scanf("%lld %lld %lld",&n,&m,&p);
init(p);
printf("%lld\n",Lucas(n,m,p));
return 0;
}
另一种
现在目标是求
Cmn%p
,p为素数(经典p=1e9+7)
虽然有 Cmn=n!m!(n−m)! ,但由于取模的性质对于除法不适用,所以 Cmn%p ≠ (n!%pm!%p∗(n−m)!%p)%p
所以需要把“除法”转换成“乘法”,才能借助取模的性质在不爆long long的情况下计算组合数。这时候就需要用到“逆元”!
逆元:对于a和p,若a*b%p≡1,则称b为a%p的逆元。
那这个逆元有什么用呢?试想一下求 (ab) %p,如果你知道b%p的逆元是c,那么就可以转变成 (ab) %p = a*c%p = (a%p)(c%p)%p
那怎么求逆元呢?这时候就要引入强大的费马小定理!
费马小定理:对于a和素数p,满足$a^{p-1}$%p≡1
接着因为 ap−1 = ap−2∗a ,所以有 ap−2∗a %p≡1!对比逆元的定义可得, ap−2 是a的逆元!
所以问题就转换成求解 ap−2 ,即变成求快速幂的问题了(当然这需要满足p为素数)。
现在总结一下求解 Cmn%p 的步骤:
- 通过循环,预先算好所有小于max_number的阶乘(%p)的结果,存到fac[max_number]里 (fac[i] = i!%p)
- 求m!%p的逆元(即求fac[m]的逆元):根据费马小定理,x%p的逆元为 xp−2 ,因此通过快速幂,求解 fac[m]p−2 %p,记为M
- 求(n-m)!%p的逆元:同理为求解 fac[n−m]p−2 %p,记为NM
- Cmn%p = ((fac[n]*M)%p*NM)%p
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
typedef long long ll;
typedef long double ld;
const ld eps=1e-10;
const int inf = 0x3f3f3f;
const int maxn = 100005;
ll fac[maxn];
ll pow_mod(ll a,ll b,ll mod)
{
ll ret = 1;
while(b)
{
if(b & 1) ret = (ret*a)%mod;
a = (a*a)%mod;
b >>= 1;
}
return ret;
}
ll Fac(ll p)
{
fac[0] = 1;
for(int i = 1;i <= p;i++)
{
fac[i] = (fac[i-1] * i)%p;
}
}
ll lucas(ll n,ll m,ll p)
{
ll ret = 1;
while(n && m)
{
ll a = n % p;
ll b = m % p;
if(a < b)
return 0;
ret = (ret*fac[a]*pow_mod(fac[b]*fac[a-b]%p,p-2,p))%p;
n /= p;
m /= p;
}
return ret%p;
}
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
ll a,b,p;
scanf("%I64d%I64d%I64d",&a,&b,&p);
Fac(p);
ll ans = lucas(a+b,b,p);
printf("%I64d\n",ans);
}
}