组合数学(费马小定理+卢卡斯定理)模板

本文深入探讨了组合数取模算法的两种实现方法:费马小定理和卢卡斯定理。通过具体的代码示例,详细讲解了如何在大数运算中高效地计算组合数,并提供了解决方案来避免溢出问题。适用于竞赛编程和大数据处理场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

组合数取模(费马小定理)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int G = 1000000;
#define mod 1000000007
ll pri[G];
ll ni[G];
ll pow(ll a,ll b,ll m){
    ll ans = 1;
    a %= m;
    while(b){
        if(b&1)ans=(ans%m)*(a%m)%m;
        b /= 2;
        a = (a%m)*(a%m)%m;
    }
    ans %= m;
    return ans;
}

int main(){
  pri[0]=1;
  ni[0]=1;
  for(int i=1;i<G;i++){
    pri[i]=pri[i-1]*i%mod;
    ni[i]=pow(pri[i],mod-2,mod);
  }
  int n,b;
  while(cin>>n>>b){
    ll ans=((pri[n]*ni[b]%mod)*ni[n-b])%mod;
    printf("%lld\n",ans);
  }
}

组合数取模(卢卡斯定理)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 100000 + 5;
ll n,m,p,fac[maxn];
ll pow(ll x,ll k){
  ll ans = 1;
  while(k){
    if(k&1) ans=ans*x%p;
    k>>=1;
    x=x*x%p;
  }
  return ans;
}
ll C(ll k,ll b){
  if(k<b) return 0;
  return fac[k]*pow(fac[b]%p,p-2)%p*pow(fac[k-b]%p,p-2)%p;
}
ll Lucas(ll k,ll b){
  if(!b) return 1;
  return C(k%p,b%p)*Lucas(k/p,b/p)%p;
}
int main(){
  int t;
  scanf("%d",&t);
  while(t--){
    fac[0]=1;
    scanf("%lld%lld%lld",&n,&m,&p);
    for(int i=1;i<=p;++i){
      fac[i]=fac[i-1]*i%p;
    }
    printf("%lld\n",Lucas(n,m));
  }
  return 0;
}
卢卡斯定理(Lucas Theorem)是组合数学中用于计算大组合数模一个素数的一种有效方法。它将大组合数拆分为多个小组合数的乘积,并利用模运算的性质来简化计算。该定理的形式化描述为: 对于非负整数 $ n, m $ 和素数 $ p $,有: $$ C(n, m) \mod p = \prod_{i=0}^{k} C(n_i, m_i) \mod p $$ 其中 $ n_i $ 和 $ m_i $ 是 $ n $ 和 $ m $ 在 $ p $ 进制下的各位数字。 ### 卢卡斯定理C++ 实现 以下是一个完整的 C++ 示例,展示如何使用卢卡斯定理计算组合数模 $ p $ 的值。该代码适用于 $ n, m $ 较大但模数 $ p $ 较小的情况(如 $ p \leq 10^5 $)。 ```cpp #include <iostream> #include <cstdio> #include <vector> using namespace std; typedef long long LL; // 快速幂求逆元 LL pow_mod(LL a, LL b, LL p) { LL res = 1; while (b > 0) { if (b & 1) res = (res * a) % p; a = (a * a) % p; b >>= 1; } return res; } // 预处理阶乘数组 LL factorial[100010]; void precompute_factorials(int p) { factorial[0] = 1; for (int i = 1; i <= p; ++i) { factorial[i] = (factorial[i - 1] * i) % p; } } // 计算组合数 C(n, m) mod p LL comb(LL n, LL m, LL p) { if (m > n) return 0; return (factorial[n] * pow_mod(factorial[m], p - 2, p) % p * pow_mod(factorial[n - m], p - 2, p)) % p; } // Lucas 定理递归实现 LL lucas(LL n, LL m, LL p) { if (m == 0) return 1; return (comb(n % p, m % p, p) * lucas(n / p, m / p, p)) % p; } int main() { int T; cin >> T; while (T--) { LL n, m; int p; cin >> n >> m >> p; precompute_factorials(p); cout << lucas(n, m, p) << endl; } return 0; } ``` ### 示例说明 - **预处理阶乘**:为了高效计算 $ C(n, m) \mod p $,先对模数 $ p $ 预处理阶乘数组,便于后续快速调用。 - **逆元计算**:使用费马小定理求模逆元,即 $ a^{-1} \equiv a^{p-2} \mod p $。 - **递归实现**:按照卢卡斯定理的递归结构,将大问题拆分为小问题,直到问题规模缩小到 $ n < p $ 为止。 ### 性能分析 - **时间复杂度**:预处理阶乘为 $ O(p) $,每次查询的时间复杂度约为 $ O(\log_p n) $。 - **适用场景**:适用于模数 $ p $ 较小(如 $ p \leq 10^5 $)但 $ n, m $ 很大的组合数取模问题。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值