组合数四大相关求法详解(附模板)

求组合数

加法递推 – O( n 2 n^2 n2

Problem A 求组合数Ⅰ

​ 给定 n 组询问,每组询问给定两个整数 a , b , 请你输出 C a b    m o d    ( 1 0 9 + 7 ) C^b_a \ \ mod ~~(10^9 + 7) Cab  mod  (109+7) 的值

1 <= n <= 100000, 1 <= b <= a <= 2000

思路

  • 假如我们有 a 个苹果,其中有 1 个是青苹果,剩下的 a - 1 个均是红苹果,现在我们要从中选取 b 个苹果,问有多少种选法
    • (1)包含青苹果,那么剩下的 b - 1 苹果,就从 a - 1中选取
    • (2)不包含青苹果,那么选取的b个苹果就从 a - 1 中选取
    • 那么我们则有 C a b = C a − 1 b − 1 + C a − 1 b \color{Orange}C^b_a =C^{b-1}_{a-1} + C^b_{a-1} Cab=Ca1b1+Ca1b
  • 如果每次询问我们都重新计算组合值,那么对于整体的时间复杂度而言太高,故此我们需要将计算过的数值存储下来,以便后序使用

代码模板

const int N = 2010, mod = 1e9 + 7;

int n, m; // C(n, m) = C(n − 1, m - 1) + C(n − 1, m);
int c[N][N]; //表示从 a 个苹果中选 b 个的方案

void init()
{
    c[1][0] = c[1][1] = 1; // 边界条件 C(n , 0) = C(n , n) = 1; 
    for(int i = 2; i < N; i ++ )
    {
        c[i][0] = 1;
        for(int j = 1; j < N; j ++ )
            c[i][j] = (1ll * c[i - 1][j - 1] + c[i - 1][j]) % mod;
    }
    
}

乘法递推 – O( n l o g n nlogn nlogn

Problem B 求组合数Ⅱ

​ 给定 n 组询问,每组询问给定两个整数 a , b , 请你输出 C a b    m o d    ( 1 0 9 + 7 ) C^b_a \ \ mod ~~(10^9 + 7) Cab  mod  (109+7) 的值

1 <= n <= 100000, 1 <= b <= a <= 10^5

如果不熟悉逆元求解的,可以点开这里食用 - > 乘法逆元(inverse element)及四大相关求法详解(含证明)

思路

  • 利用公式 C n m = n   ! ( n   −   m )   !   ×   m   ! \color{Orange}C^m_n = \frac{n~!}{(n ~-~ m)~!~×~ m~!} Cnm=(n  m) ! × m !n !

  • 由于模数 mod = 1e9 + 7为质数,可以用费马小定理求逆元,故我们可以先预处理出所有阶乘取模的余数 fact[N],阶乘取模的逆元 invfact[N]

代码模板

typedef long long LL;
const int N = 1e5 + 10, mod = 1e9 + 7;

LL fact[N];// 阶乘 ,分子
LL invfact[N]; // 阶乘逆元,即分母

LL qmi(LL a, LL k, LL p)
{
    LL res = 1;
    while(k)
    {
        if(k & 1) res = (LL) res * a % p;
        a = (LL) a * a % p;
        k >>= 1;
    }
    
    return res;
}
void init()
{
    fact[0] = invfact[0] = 1;
    
    for(int i = 1; i < N; i ++ )
    {
        fact[i] = (LL) fact[i] * i % mod; // n ! = n *(n - 1) *... * 1;
        invfact[i] = (LL) qmi(fact[i], mod - 2, mod) % mod;
    }
}
LL C(LL a, LL b)
{
    if(a < b) return 0;
    if(b == 0 || a == b) return 1;
    
    return 1ll * fact[a] * invfact[a - b] % mod * invfact[b] % mod;
}

Lucas 定理 – O( l o g p n × p l o g p log_p^n × p logp logpn×plogp)

Problem C 求组合数Ⅲ

​ 给定 n 组询问,每组询问给定三个整数a, b ,p,其中 p 是质数,请你输出 C a b   m o d   p C^b_a~ mod~p Cab mod p 的值

1 <= n <= 20,1 <= b <= a <= 10^18 , 1 <= p <= 10^5

思路

Lucas 定理:

若 p 为质数,则对于任意的整数 1 < = m < = n 1<= m < = n 1<=m<=n, 有:
( n m ) = ( n   m o d   p m   m o d   p ) ∗ ( ⌊ n p ⌋ ⌊ m p ⌋ )   ( m o d   p ) \dbinom{n}{m} =\dbinom{n ~ mod~ p}{m ~ mod ~ p} * \dbinom{\lfloor{\frac{n}{p}}\rfloor}{\lfloor{\frac{m}{p}}\rfloor} ~(mod~p) (mn)=(m mod pn mod p)(pmpn) (mod p)

  • 利用利用快速幂求出 ( n   m o d   p m   m o d   p ) \dbinom{n ~ mod~ p}{m ~ mod ~ p} (m mod pn mod p) 组合数
  • 递归 Lucas 求解 ( ⌊ n p ⌋ ⌊ m p ⌋ ) \dbinom{\lfloor{\frac{n}{p}}\rfloor}{\lfloor{\frac{m}{p}}\rfloor} (pmpn)

代码模板

typedef long long LL;

LL qmi(LL a, LL k, LL p)
{
    LL res = 1;
    while(k & 1)
    {
        if(k & 1) res = (LL) res * a % p;
        a = (LL) a * a % p;
        k >>= 1;
    }
    return res;
}
LL C(LL a, LL b, LL p)
{
    if(a < b) return o;
    
    LL up = 1, down = 1;
    for(int i = a, j = 1; j <= b; i --, j ++ ) // 
    {
        up = (LL) up * i % p;
        down = (LL) down * j % p;
        
    }
    
    return up * (LL) qmi(down, p - 2, p) % p;
}
LL Lucas(LL a, LL b, LL p)
{
    if(a < p && b < p) return C(a, b, p);
    
    return (LL) C(a % p, b % p, p) * Lucas(a/p, b/p, p) % p;
}

求大组合数 (高精度)

Problem D 求组合数Ⅳ

​ 给定两个整数 a , b,求 C a b C^b_a Cab 的值

注意结果可能很大,需要用高精度计算

1 <= b <= a <= 5000

思路

当我们需要求出组合数的真实值,而非对某个数的余数时,分解质因数的方式比较好用

  1. 筛法 求出范围内的所有质数

  2. 通过 C a b = a   ! b   ! × ( a − b )   ! \color{Orange} C^b_a = \frac{a~!}{b~!×(a - b)~!} Cab=b !×(ab) !a ! 求出每个质因子的次数

    • 知 识 点 \color{Turquoise}知识点 :求 a !质因数的次数

      (分解质因数) = p 1 α 1 ∗ p 2 α 2 ∗ . . . ∗ p k α k p_1^{\alpha_1}*p_2^{\alpha_2}*...*p_k^{\alpha_k} p1α1p2α2...pkαk

      求 a ! 分解每个 p的次数 :
      c n t ( a   ! ) = ⌊ a p ⌋ 下 取 整 + ⌊ a p 2 ⌋ 下 取 整 + ⌊ a p 3 ⌋ 下 取 整 + ⋅ ⋅ ⋅ cnt(a~!) = \lfloor{\frac{a}{p}\rfloor}^{下取整} +\lfloor{\frac{a}{p^2}\rfloor}^{下取整} +\lfloor{\frac{a}{p^3}\rfloor}^{下取整} + ··· cnt(a !)=pa+p2a+p3a+

         = p 的 倍 数 中 ∈ [ 1 , a ] 的 个 数 + p 2 的 倍 数 中 ∈ [ 1 , a ] 的 个 数 + … ~~=p的倍数中^{∈[1,a]的个数}+p^2的倍数中^{∈[1,a]的个数}+…   =p[1,a]+p2[1,a]+

      为什么这样计算 p 的次数只 + 1,而不是 + k,因为 a 中 p k p ^k pk 低阶项的次数已经被计算过了,比如 p 3 p^3 p3 既是 p 的倍数也是 p 2 p^2 p2 的倍数

      例如,当我们求 8 ! = 1 × 2 × 3 × 4 × 5 × 6 × 7 × 8 8 ! = 1 ×2 ×3 ×4 ×5 ×6 ×7 ×8 8!=1×2×3×4×5×6×7×8, 其中 2 的个数

      • 1 ~ 8 之间 含 2 一次的数有 2,4,6,8 四个 2
      • 含 2 的二次的数有 4,8 ,但 4, 8 中 2 的一次方此前已经记过 一次了,故只加二次方中的 两个 2
      • 含 2 的三次方的数有 8,但 8 中 的 一次,二次方都被记过一次了,故只加三次方中的 一个 2

      同理可以类推到 p k p^k pk 它的幂次中的前 k - 1个 p 在 计算 p 1 p^1 p1 ~ p k − 1 p^{k-1} pk1 次数都已经算过了 1次了

  3. 利用高精度乘法将所有质因子相乘

  • 故对于 C a b = a   ! b   ! × ( a − b )   ! C^b_a = \frac{a~!}{b~!×(a - b)~!} Cab=b !×(ab) !a ! 中 质因子的次数 为
    ∏ i p i 分 子 次 数 和 − 分 母 次 数 和 \color{Orange}{\prod_ip_i^{分子次数和 - 分母次数和}} ipi

代码模板

int primes[N], cnt; // prime[] 存储所有素数
bool st[N];// st[x] 存储 x 是否被筛掉
void get_primes(int n)
{
    for(int i = 2; i <= n; i ++ )
    {
        if(!st[i]) primes[cnt++] = i;
        
        for(int j = 0; primes[j] <= n / i; j ++ )
        {
            st[primes[j] * i] = true;
            if(i % primes[j] == 0) break;
        }
    }
}

int get(int n, int p) // 计算 质因子
{
    int res = 0;
    while(n)
    {
        res +=  n / p;
        n /= p;
    }
    return res;
}
vector<int> mul(vector<int> &a, int b) // 高精度乘法
{
    vector<int> c;
    int t = 0;
    for(int i = 0; i < a.size(); i ++ )
    {
        t += a[i] * b;
        c.push_back(t % 10);
        t /= 10;
    }
    
    while(t) c.push_back(t % 10), t /= 10;
    while(c.size() > 1 && c.back() == 0) c.pop_back();
    
    return c;
}

int sum[N];// 储存c[a][b] 中某个质因子p 的次数

get_primes(a); // 预处理范围内的所有质数

for(int i = 0; i < cnt; i ++ ) // 得到C[a][b]中p的次数:分子的次数 - 分母的次数
{
    int p = primes[i];
    sum[i] = get(a, p) - get(a - b, p) - get(b, p);
} 

vector<int> res;
res.push_back(1);// 放入一个 1,与 质因数高精度相乘

for(int i = 0; i< cnt; i ++ )	// 用高精度将所有质因子相乘
   for(int j = 0; j < sum[i]; j ++ )
       res = mul(res, primes[i]);

for(int i = res.size() - 1; i >= 0; i -- ) printf("%d", res[i]);// 输出组合值
以上内容尚未完全,随着今后学习的推进,我会继续对其进行补充与完善。另外,大家如果觉得我写的还行的话,还请赠予我一个可爱的赞,你的赞对于我是莫大的支持
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值