P3807 【模板】卢卡斯定理

刷数论模板啦。。。

在正事之前发现了一个小知识:组合数有两种书写方式。一种是形如\(C_3^2\),另一种是形如\(\binom{3}{2}\)。这两者等价。

卢卡斯定理其实也简单的:

\[\binom{m}{n}=\binom{m \mod p}{n \mod p} \times \binom{\lfloor \frac{m}{p} \rfloor }{ \lfloor \frac{n}{p} \rfloor} \mod p\]

这是一种简单的表达式。

lucas定理可以求大\(n,m\)的组合数取模。

这是一种递归的算法。下取整的大部分我们可以再次递归缩小范围求解,而取模的部分我们直接暴力来做。

如何暴力?

组合数的定义式:

\[C_m^n=\frac{m!}{n!(m-n)!}\]

在模意义下我们就没除法了。所以我们就要用到乘法逆元了。

我们先预处理出这些阶乘,然后可以用任意一种方法来求这两个阶乘的逆元。暴力乘上去即可。

代码:

#include<cstdio>

const int maxn = 1000005;
#define ll long long
ll n, m, p;
ll frac[maxn];
ll pow_mod(ll x, ll y, ll p)
{
    ll ans = 1, base = x % p;
    while(y)
    {
        if(y & 1) ans = ans * base % p;
        base = base * base % p;
        y >>= 1;
    }
    return ans % p;
}
ll inv(ll x, ll p)
{
    return pow_mod(x, p - 2, p) % p;
}
ll C(ll x, ll y, ll p)
{
    if(x < y) return 0;
    return frac[x] * inv(frac[y], p) % p * inv(frac[x - y], p) % p;
}
ll lucas(ll x, ll y, ll p)
{
    if(y == 0) return 1;
    return C(x % p, y % p, p) * lucas(x / p, y / p, p) % p;
}
int main()
{
    int T; scanf("%d", &T);
    while(T--)
    {
        scanf("%lld%lld%lld", &n, &m, &p);
        frac[0] = 1;
        for(int i = 1; i <= p; i++) frac[i] = frac[i - 1] * i % p;
        printf("%lld\n", lucas(n + m, n, p));
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/Garen-Wang/p/9745873.html