Appearance
L8_01 计数原理
一、加法原理
1.1 加法原理定义
若完成一件事有 n 类不同的方法,第 i 类方法有 m_i 种,则完成这件事共有 m_1 + m_2 + ... + m_n 种方法。
1.2 加法原理示例
cpp
// 计算从A地到C地的路线数
// A到B有3条路,B到C有2条路
// A到D有4条路,D到C有3条路
int routes = 3 * 2 + 4 * 3; // 18条路线1.3 加法原理应用
cpp
// 计算集合的并集大小
// |A ∪ B| = |A| + |B| - |A ∩ B|
int unionSize(int a, int b, int intersection) {
return a + b - intersection;
}
// 计算三个集合的并集
// |A ∪ B ∪ C| = |A| + |B| + |C| - |A ∩ B| - |A ∩ C| - |B ∩ C| + |A ∩ B ∩ C|
int unionSize3(int a, int b, int c, int ab, int ac, int bc, int abc) {
return a + b + c - ab - ac - bc + abc;
}二、乘法原理
2.1 乘法原理定义
若完成一件事需要 n 个步骤,第 i 个步骤有 m_i 种方法,则完成这件事共有 m_1 × m_2 × ... × m_n 种方法。
2.2 乘法原理示例
cpp
// 计算密码组合数
// 密码长度为4,每位可以是数字(0-9)或字母(a-z)
int passwordCombinations = pow(36, 4); // 36^4 = 16796162.3 乘法原理应用
cpp
// 计算排列数 P(n, k) = n × (n-1) × ... × (n-k+1)
long long permutation(int n, int k) {
long long result = 1;
for (int i = 0; i < k; i++) {
result *= (n - i);
}
return result;
}
// 计算组合数 C(n, k) = n! / (k! × (n-k)!)
long long combination(int n, int k) {
if (k > n) return 0;
if (k == 0 || k == n) return 1;
k = min(k, n - k);
long long result = 1;
for (int i = 0; i < k; i++) {
result *= (n - i);
result /= (i + 1);
}
return result;
}三、容斥原理
3.1 容斥原理概述
容斥原理用于计算多个集合的并集大小。
3.2 容斥原理公式
cpp
// 两个集合
// |A ∪ B| = |A| + |B| - |A ∩ B|
// 三个集合
// |A ∪ B ∪ C| = |A| + |B| + |C| - |A ∩ B| - |A ∩ C| - |B ∩ C| + |A ∩ B ∩ C|
// n个集合
// |A₁ ∪ A₂ ∪ ... ∪ Aₙ| = Σ|Aᵢ| - Σ|Aᵢ ∩ Aⱼ| + Σ|Aᵢ ∩ Aⱼ ∩ Aₖ| - ... + (-1)ⁿ⁺¹|A₁ ∩ ... ∩ Aₙ|3.3 容斥原理实现
cpp
// 计算[1, n]中能被a或b整除的数的个数
int countDivisible(int n, int a, int b) {
int countA = n / a;
int countB = n / b;
int countAB = n / lcm(a, b);
return countA + countB - countAB;
}
// 计算最小公倍数
int lcm(int a, int b) {
return a * b / gcd(a, b);
}
// 计算最大公约数
int gcd(int a, int b) {
while (b) {
a %= b;
swap(a, b);
}
return a;
}3.4 容斥原理应用:错排问题
cpp
// 计算n个元素的错排数
// D(n) = n! × (1 - 1/1! + 1/2! - 1/3! + ... + (-1)^n / n!)
long long derangement(int n) {
long long result = 0;
long long factorial = 1;
for (int i = 0; i <= n; i++) {
if (i > 0) factorial *= i;
if (i % 2 == 0) {
result += factorial;
} else {
result -= factorial;
}
}
return result;
}四、鸽巢原理
4.1 鸽巢原理定义
若有 n+1 个物体放入 n 个盒子中,则至少有一个盒子包含不少于 2 个物体。
4.2 鸽巢原理扩展
若有 kn+1 个物体放入 n 个盒子中,则至少有一个盒子包含不少于 k+1 个物体。
4.3 鸽巢原理应用
cpp
// 判断是否存在重复元素
bool hasDuplicate(vector<int>& nums) {
unordered_set<int> seen;
for (int num : nums) {
if (seen.count(num)) return true;
seen.insert(num);
}
return false;
}
// 证明在任何367个人中至少有两人同一天生日
bool hasSameBirthday(int n) {
return n > 366;
}五、排列组合综合应用
5.1 多重集合排列
cpp
// 计算多重集合的排列数
// 有n个元素,其中有k种不同元素,每种元素的个数分别为n₁, n₂, ..., nₖ
// 排列数 = n! / (n₁! × n₂! × ... × nₖ!)
long long multisetPermutation(vector<int>& counts) {
int total = 0;
long long result = 1;
for (int c : counts) {
total += c;
for (int i = 2; i <= c; i++) {
// 计算阶乘并约分
}
}
// 计算 total!
for (int i = 2; i <= total; i++) {
result *= i;
}
// 除以各元素阶乘
for (int c : counts) {
long long fact = 1;
for (int i = 2; i <= c; i++) {
fact *= i;
}
result /= fact;
}
return result;
}5.2 隔板法
cpp
// 计算将n个相同的球放入k个不同盒子的方法数
// C(n+k-1, k-1)
long long starsAndBars(int n, int k) {
return combination(n + k - 1, k - 1);
}
// 计算将n个相同的球放入k个不同盒子,每个盒子至少有一个球的方法数
// C(n-1, k-1)
long long starsAndBarsAtLeastOne(int n, int k) {
if (n < k) return 0;
return combination(n - 1, k - 1);
}