[组合] 组合数计算四大算法模板(模板+卢卡斯定理)

article/2025/9/23 17:42:13

文章目录

    • 0. 前言
    • 1. 预处理组合数+组合递推式
    • 2. 预处理阶乘+逆元
    • 3. 卢卡斯定理
    • 4. 高精度组合数

0. 前言

组合数求解有很多种方式,不同的方式对应这不同的时间复杂度,难以程度也是不尽相同。根据数据范围选择对应的方法即可。

1. 预处理组合数+组合递推式

885. 求组合数 I
在这里插入图片描述
重点: 组合公式、组合递推式

组合公式:
C a b = ( a b ) = a × ( a − 1 ) × ⋯ × ( a − b + 1 ) 1 × 2 × 3 × ⋯ × b = a ! b ! ( a − b ) ! C_{a}^{b}=\tbinom{a}{b}=\frac {a\times(a-1)\times\cdots\times(a-b+1)} {1\times 2 \times 3 \times\cdots \times b}=\frac {a!} {b!(a-b)!} Cab=(ba)=1×2×3××ba×(a1)××(ab+1)=b!(ab)!a!

时间复杂度分析:

  • 10w 组询问
  • 一个组合数最坏需要 2000 次运算
  • 则暴力总共的时间复杂度为 2000 * 10w,为 2 亿次运算,这妥妥的超时。
  • 但是能发现,ab 都比较小,总共可能出现的 (a,b) 对就是 2000 * 2000 = 400w 对不同的 C a b C_a^b Cab
  • 所以可以预处理出来所有的情况,有组合递推式 C a b = C a − 1 b + C a − 1 b − 1 C_a^b=C_{a-1}^b +C_{a-1}^{b-1} Cab=Ca1b+Ca1b1从实际出发 C a b C_a^b Cab 表示从 a a a 个苹果当中选 b b b 个苹果的方案数,那么可以将所有选法分为两种情况,即从 n n n 个苹果中挑出来一个苹果,这个苹果被选中,这个苹果不被选中,就这两种情况。 则包含这个特殊苹果的方案数就是 C a − 1 b − 1 C_{a-1}^{b-1} Ca1b1,不包含这个特殊苹果的方案数是 C a − 1 b C_{a-1}^{b} Ca1b
#include <iostream>using namespace std;typedef long long LL;const int N = 2010, MOD = 1e9+7;int n;
int c[N][N];void init() {for (int i = 0; i < N; ++i) for (int j = 0; j <= i; ++j)if (!j) c[i][j] = 1;else c[i][j] = (c[i-1][j] + c[i-1][j-1]) % MOD;
}int main() {init();int n;cin >> n;while (n --) {int a, b;cin >> a >> b;cout << c[a][b] << endl;}return 0;
}

2. 预处理阶乘+逆元

886. 求组合数 II
在这里插入图片描述

重点: 组合公式、组合递推式

组合公式:
C a b = a ! b ! ( a − b ) ! C_{a}^{b}=\frac {a!} {b!(a-b)!} Cab=b!(ab)!a!

预处理所有的模 p p p 意义下的阶乘,我们假设用 f a c t [ i ] = i ! % ( 1 0 9 + 7 ) fact[i]=i!\%(10^9+7) fact[i]=i!%(109+7)。这个事情是简单的,所以分子分母都可以简单的得到。但是众所周知 a b % p ≠ a % p b % p \frac a b \%p \neq \frac {a\%p}{b\%p} ba%p=b%pa%p
但是,我们可以采用逆元将除法变成乘法

所以我们可以再预处理出 i n f a c t [ i ] = ( i ! ) − 1 % ( 1 0 9 + 7 ) infact[i]=(i!)^{-1} \% (10^9+7) infact[i]=(i!)1%(109+7)

此时,有: C a b = f a c t [ a ] × i n f a c t [ b − a ] × i n f a c t [ b ] C_{a}^{b}=fact[a] \times infact[b-a] \times infact[b] Cab=fact[a]×infact[ba]×infact[b]

求逆元,由于 p=1e9+7,那么可以采用快速幂和费马小定理,时间复杂度就是 O ( n l o g n ) O(nlogn) O(nlogn)

注意:

  • 能开 long long 的地方建议全开
  • 数组中间计算值也是相当容易溢出的,记得及时对中间结果取模
#include <iostream>
#include <algorithm>using namespace std;typedef long long LL;const int N = 1e5+5, MOD = 1e9+7;int fact[N], infact[N];int qmi(int a, int k, int p) {LL res = 1;while (k) {if (k & 1) res = (LL)res * a % p;k >>= 1;a = (LL)a * a % p;}return res;
}int main() {fact[0] = infact[0] = 1;for (int i = 1 ; i < N; ++i) {fact[i] = (LL)fact[i - 1] * i % MOD;infact[i] = (LL)infact[i - 1] * qmi(i, MOD - 2, MOD) % MOD;}int n;cin >> n;while (n --) {int a, b;cin >> a >> b;cout << (LL)fact[a] * infact[a - b] % MOD * infact[b] % MOD << endl;}return 0;
}

3. 卢卡斯定理

887. 求组合数 III
在这里插入图片描述

重点: 卢卡斯定理

在这里插入图片描述
在此推荐一个大佬博客,很清楚,简介

再推荐一个,我的证明就是仿照这位大佬的

模板代码:

#include <iostream>using namespace std;typedef long long LL;int qmi(int a, int k, int p) {int res = 1 % p;while (k) {if (k & 1) res = (LL)res * a % p;a = (LL)a * a % p;k >>= 1;}return res;
}// 定义求解
int C(int a, int b, int p) {if (b > a) return 0;int res = 1;for (int i = 1, j = a; i <= b; ++i, --j) {res = (LL)res * j % p;res = (LL)res * qmi(i, p - 2, p) % p;}return res;
}int lucas(LL a, LL b, LL p) {               // 注意LL参数类型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;     // 递归让其到p范围内求解
}int main() {int n;cin >> n;while (n --) {LL a, b, p;cin >> a >> b >> p;cout << (LL)lucas(a, b, p) << endl;}return 0;
}

4. 高精度组合数

888. 求组合数 IV

在这里插入图片描述

重点: 高精度组合数、高精乘、分解质因数

直接从组合公式出发求解:
C a b = ( a b ) = a × ( a − 1 ) × ⋯ × ( a − b + 1 ) 1 × 2 × 3 × ⋯ × b = a ! b ! ( a − b ) ! C_{a}^{b}=\tbinom{a}{b}=\frac {a\times(a-1)\times\cdots\times(a-b+1)} {1\times 2 \times 3 \times\cdots \times b}=\frac {a!} {b!(a-b)!} Cab=(ba)=1×2×3××ba×(a1)××(ab+1)=b!(ab)!a!

但是,这样显然需要实现高精乘、高精除这两个。

故,我们可以对其先分解质因数,然后组合结果保证为整数,即只需要实现高精乘即可。在此分解质因数也是采用组合公式的第二个,直接对阶乘进行质因数分解。

思路:

  • 预处理 5000 内所有素数

  • a! 的唯一分解式中求解素数 p 的倍数有一个快速的方法:a! = a/p + a/(p^2) + a/(p^3) +... 这里的所有除均为下取整,直至 p^k 大于 a。这样做的意义在于 a/p 可以得到 1 到 n 中有多少个数是 p 的倍数,a/(p^2) 理解为得到 1 到 n 中有多少个数是 p^2 的倍数…依次类推

  • 这个乍一看肯定计算重复项了啊,但是实际上并没有。可以手动模拟一下 a = 8,p =2 的情况,注意这里是求解 8! 中质因子 2 出现的次数,此时结果应该是 7,因为 2,4,6,8 分别贡献 p 作为 2 时的 4 个数。 4,8 贡献了 p 作为 2^2 时的 2 个数,8 贡献了 p 作为 2^3 时的 1 个数。总共就是 7 个数

  • 根据这个性质,求解阶乘中某个质因子出现的次数,一般来讲有两种写法,朴素版本和优化版本,代码均已给出

模板代码:

#include <iostream>
#include <algorithm>
#include <vector>using namespace std;const int N = 5005;int primes[N], cnt;
int sum[N];
bool st[N];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,t = p;while (n >= p) {res += n / p;p *= t;}return res;
}// 精炼简介,完美版本
int get1(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;}return C;
}int main() {int a, b;cin >> a >> b;get_primes(a);for (int i = 0; i < cnt; ++i) {int p = primes[i];sum[i] = get(a, p) - get(b, p) - get(a - b, p);}vector<int> res;res.push_back(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) cout << res[i];cout << endl;return 0;
}

http://chatgpt.dhexx.cn/article/CnQOanHF.shtml

相关文章

组合数

1、定义&#xff1a;从m个不同元素中&#xff0c;任取n(n≤m)个元素并成一组&#xff0c;叫做从m个不同元素中取出n个元素的一个组合&#xff1b;从m个不同元素中取出n(n≤m)个元素的所有组合的个数&#xff0c;叫做从m个不同元素中取出n个元素的组合数。 2、公式&#xff1a;…

组合数取模总结

题目描述&#xff1a; 在ACM竞赛现在叫JB竞赛&#xff1f;中&#xff0c;经常会遇到组合数取模的题目&#xff1b;就我现在的水平而言&#xff0c;大概分为以下三类&#xff0c;以后遇到新的方法会在做补充&#xff1b; 第一种&#xff1a; n和m都较小 &#xff08;<1000&a…

逆元求组合数

逆元简介 同余符号 ≡ 先bb一下 ≡&#xff0c;这个符号有三个意思&#xff0c;再这里用到的意思为“同余符号”。≡ 的介绍 两个整数a&#xff0c;b&#xff0c;若它们除以整数m所得的余数相等&#xff0c;则称a&#xff0c;b对于模m同余 记作a≡b(mod m) 读作a同余于b模m&am…

组合数(Combinatorial_Number)

定义&#xff1a; 从n个不同元素中&#xff0c;任取m(m≤n)个元素并成一组&#xff0c;叫做从n个不同元素中取出m个元素的一个组合;从n个不同元素中取出m(m≤n)个元素的所有组合的个数&#xff0c;叫做从n个不同元素中取出m个元素的组合数。 公式&#xff1a; 在线性写法中被…

组合数的计算

本篇博客来自南昌理工学院acm集训队成员yyj 组合数 1.定义 组合数&#xff1a;从 n 个不同元素中每次取出 m 个不同元素 &#xff0c;不管其顺序合成一组&#xff0c;称为从 n 个元素中不重复地选取 m 个元素的一个组合。所有这样的组合的种数称为组合数。 2.性质与描述 2…

组合数的性质证明

性质 极其简单的证明 根据杨辉三角的性质&#xff0c;第x行第y个的值为C(x-1,y-1) 那么 一目了然。 严肃的证明

组合数性质--二项式系数之和等于2^n的证明

1.公式 首先我们都知道组合数的意义&#xff0c;就是说一共有n个样本&#xff0c;一次性从中取出m个样本&#xff0c;一共有多少种不同的取法。它的公式如下&#xff1a; 它有这么一个性质&#xff1a; 该性质有若干种证明方式&#xff0c;今天我在这边写出我觉得挺巧妙的一种…

组合数原理

资料来源&#xff1a;https://baike.baidu.com/ 组合数 从n个不同元素中&#xff0c;任取m(m≤n)个元素并成一组&#xff0c;叫做从n个不同元素中取出m个元素的一个组合&#xff1b;从n个不同元素中取出m(m≤n)个元素的所有组合的个数&#xff0c;叫做从n个不同元素中取出m个…

深入详细理解矩阵 (矩阵的加减乘、转置、共轭、共轭转置)

简介 矩阵:英文名Matrix。在数学名词中&#xff0c;矩阵用来表示统计数据等方面的各种有关联的数据。这个定义很好地解释了Matrix代码制造世界的数学逻辑基础。矩阵是数学中最重要的基本概念之一&#xff0c;是代数学的一个主要研究对象&#xff0c;也是数学研究及应用的一个重…

【线性代数之二】矩阵与行列式

一、矩阵 1.1 定义 由 m n 个数aij排成的m行n列的数表称为m行n列的矩阵&#xff0c;简称m n矩阵。记作&#xff1a; 这mn 个数称为矩阵A的元素&#xff0c;简称为元&#xff0c;数aij位于矩阵A的第i行第j列&#xff0c;称为矩阵A的(i,j)元&#xff0c;以数 aij为(i,j)元的…

CString类型转换为LPCSTR类型

今天编程遇到一个问题&#xff0c;就是openGL中某个函数需要传入LPCSTR类型的参数&#xff0c;而通过MFC对话框获取得到的是CString类型的参数&#xff0c;因此需要将CString转化为LPCSTR类型&#xff0c;网上有很多这样的强转类型&#xff0c;然而却发现在强转的时候没有用&am…

linux jstat 简介

本文目录一览&#xff1a; 1、Linux使用jstat命令查看jvm的GC情况2、linux怎么监控 jvm内存 jstat3、Linux系统监控要用到哪些命令4、linux上如何安装jstatd服务 Linux使用jstat命令查看jvm的GC情况 Linux 使用jstat命令查看jvm的GC情况 命令格式 jstat命令命令格式&#…

内存屏障与volatile(C语言版)

内存屏障与volatile&#xff08;C语言版&#xff09; 最有价值的写在最前面 内存屏障与volatile是高并发编程中比较常用的两个技术&#xff0c;无锁队列的时候就会用到这两项技术。然而这两项技术设计比较广的基础知识&#xff0c;所以比较难以理解&#xff0c;也比较不容易解…

WDM驱动

WDM英文Windows Driver Model(WDM)的缩写。 简介 WDM WDM是WINDOWS2000认证的驱动程序&#xff0c;WIN2000由NT发展而来&#xff0c;所以对于设备的支持功能有限&#xff0c;同时为了最大限度的保障稳定性&#xff0c;所以推崇WDM驱动&#xff0c;但同时WDM驱动也就是功能最少…

快速转换dBm与W

dBm是一个表示功率绝对值的值&#xff08;也可以认为是以1mW功率为基准的一个比值&#xff09;&#xff0c;计算公式为&#xff1a;10log&#xff08;功率值/1mw&#xff09;。 这里我们介绍一种将dBm转换为W的口算方法&#xff0c;这一方法总结起来就是 “1个基准”和“2个原则…

学内核之十一:ARM64屏障指令使用指南

关于屏障指令&#xff0c;主要是与乱序有关。特别是在内核开发中&#xff0c;这是一个非常重要的主题。屏障由乱序引起&#xff0c;乱序则是由优化引起。 自从摩尔定律不断被逼近极限&#xff0c;半导体的优化不再单纯通过提升频率来实现。多核心、并发执行变成了主流的优化思路…

WTM

WTM的由来 WalkingTec.Mvvm框架&#xff08;简称WTM&#xff09;最早开发与2013年&#xff0c;基于Asp.net MVC3 和 最早的Entity Framework, 当初主要是为了解决公司内部开发效率低&#xff0c;代码风格不统一的问题。经历了四年间数十个项目的考验&#xff0c;框架逐步的完善…