AcWing 887:求组合数 III ← 卢卡斯定理 / Lucas 定理

【题目来源】
https://www.acwing.com/problem/content/889/
https://www.luogu.com.cn/problem/P3807

【题目描述】
给定 n 组询问,每组询问给定三个整数 a,b,p,其中 p 是质数,请你输出 C(a,b) mod p 的值。

【输入格式】
第一行包含整数 n。
接下来 n 行,每行包含一组 a,b,p。

【输出格式】
共 n 行,每行输出一个询问的解。

【输入样例】
3
5 3 7
3 1 5
6 4 13

【输出样例】
3
3
2

【数据范围】
1≤n≤20,
1≤b≤a≤10^18,
1≤p≤10^5。

【算法分析】
● 本题与“AcWing 885:求组合数 I”(https://blog.csdn.net/hnjzsyjyj/article/details/156116093)的区别仅仅在于 a、b 的数据规模不同,达到了 10^18。

● C(n, m) 在数学中通常表示‌组合数‌,即从 n 个不同元素中选取 m 个元素的组合数目,计算公式为:n! / (m! * (n-m)!)。

● 组合数在“数位DP”中经常用到。

● 数位DP问题中经常用到的“组合数”公式:C(i, j)=C(i-1, j-1)+C(i-1, j)

● 阶乘逆元的计算核心是‌费马小定理‌。当模数 p 为质数时,费马小定理给出 a^(p-1) ≡ 1 (mod p),从而 a × a^(p-2) ≡ 1 (mod p),表明 a 的逆元为 a^(p-2) mod p。‌

● 快速幂是计算 a^(p-2) mod p 的高效工具,定义为 fastPow(a, p-2, p)。它将逆元求解的时间复杂度降至 O(log p)。‌

int fastPow(LL a,LL n,LL p) {
    LL ans=1;
    while(n) {
        if(n & 1) ans=ans*a%p;
        n>>=1;
        a=a*a%p;
    }
    return ans%p;
}

快速幂求逆元仅适用于模数 p 为质数的情况。

● 代码中 MOD=1e9+7 是常用质数。

● 定义 fac[i]=i! % p,invfac[i]=(i!)^(-1) % p。其中,p 为质数。
已知 1/i! = 1/(i-1)! * 1/i,而 1/i 为 i 的逆元,利用费马小定理知 i 的逆元等于 i^(p-2) mod p。综上可得,invfac[i] = invfac[i-1] * fastPow(a, p-2, p)

● Lucas 定理是数论中用于计算组合数 C(n, m) 模素数 p 的值的经典定理,由法国数学家 Édouard Lucas 提出。其核心思想是将大数的组合数模运算分解为更小规模的子问题,适用于模数为素数的应用场景。

● 算法竞赛中,常用 Lucas 定理的如下形式。
C(n, m) ≡ C(n%p, m%p) • C(n/p, m/p) (mod p),其中 p 为素数。

● Lucas 定理的 C++ 代码如下所示。

typedef long long LL;
 
LL C(LL n,LL m,LL p) {
    if(m>n) return 0;
    return fac[n]*fastPow(fac[m],p-2,p)*fastPow(fac[n-m],p-2,p)%p;
}
 
LL Lucas(LL n,LL m,LL p) {
    if(m==0) return 1;
    else return C(n%p,m%p,p)*(Lucas(n/p,m/p,p))%p;
}


【算法代码】
本题代码与“洛谷 P3807:卢卡斯定理 / Lucas 定理(https://blog.csdn.net/hnjzsyjyj/article/details/156147416)”基本一致。

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;
const int N=1e5+5;
LL fac[N];
int T;

int fastPow(LL a,LL n,LL p) {
    LL ans=1;
    while(n) {
        if(n & 1) ans=ans*a%p;
        n>>=1;
        a=a*a%p;
    }
    return ans%p;
}

LL C(LL n,LL m,LL p) {
    if(m>n) return 0;
    return fac[n]*fastPow(fac[m],p-2,p)*fastPow(fac[n-m],p-2,p)%p;
}

LL Lucas(LL n,LL m,LL p) {
    if(m==0) return 1;
    else return C(n%p,m%p,p)*(Lucas(n/p,m/p,p))%p;
}

int main() {
    fac[0]=1;
    cin>>T;
    while(T--) {
        LL n,m,p;
        cin>>n>>m>>p;
        for(LL i=1; i<=p; i++) {
            fac[i]=(fac[i-1]*i)%p;
        }
        cout<<Lucas(n,m,p)%p<<endl;
    }

    return 0;
}

/*
in:
3
5 3 7
3 1 5
6 4 13

out:
3
3
2
*/





【参考文献】
https://blog.csdn.net/hnjzsyjyj/article/details/156116093
https://blog.csdn.net/hnjzsyjyj/article/details/156121947
https://www.acwing.com/blog/content/406/
https://www.acwing.com/solution/content/26553/
 

posted @ 2025-12-26 08:42  Triwa  阅读(49)  评论(0)    收藏  举报