欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 游戏 > 数据结构与算法学习笔记----容斥原理

数据结构与算法学习笔记----容斥原理

2025/2/7 10:40:48 来源:https://blog.csdn.net/weixin_60278491/article/details/145401898  浏览:    关键词:数据结构与算法学习笔记----容斥原理

数据结构与算法学习笔记----容斥原理

@@ author: 明月清了个风
@@ first publish time: 2025.1.30

ps⭐️介绍了容斥原理的相关内容以及一道对应的应用例题。


Acwing 890. 能被整除的数

[原题链接](890. 能被整除的数 - AcWing题库)

给定一个整数 n n n m m m个不同的质数 p 1 , p 2 , ⋯ , p m p_1,p_2,\cdots,p_m p1,p2,,pm

请你求出 1 ∼ n 1 \sim n 1n中能被 p 1 , p 2 , ⋯ , p m p_1,p_2,\cdots,p_m p1,p2,,pm中的至少一个数整除的整数有多少个。

输入格式

第一行包含整数 n n n m m m

第二行包含 m m m个质数。

输出格式

输出一个整数,表示满足条件的整数的个数。

数据范围

1 ≤ m ≤ 16 1 \le m \le 16 1m16,

1 ≤ n , p i ≤ 1 0 9 1 \le n, p_i \le 10^9 1n,pi109

思路

容斥原理是一种重要的组合数学方法,用于解决多个集合的元素计数问题。它的核心思想是通过对集合进行交集与并集的操作,减去重复计算的部分,从而准确地计算出多个集合的并集中元素的总数。

  1. 两个集合的容斥原理

A A A B B B是两个集合,则它们的并集 A ∪ B A \cup B AB的元素个数为:

∣ A ∪ B ∣ = ∣ A ∣ + ∣ B ∣ − ∣ A ∩ B ∣ |A \cup B| = |A| + |B| - |A \cap B| AB=A+BAB

其中, ∣ A ∣ |A| A ∣ B ∣ |B| B分别是集合 A A A B B B的元素个数, ∣ A ∩ B ∣ |A \cap B| AB是集合 A A A B B B的交集的元素个数。

  1. 三个集合的容斥原理

A A A B B B C C C是三个集合,则它们的并集 A ∪ B ∪ C A \cup B \cup C ABC的元素个数为:

∣ A ∪ B ∪ C ∣ = ∣ A ∣ + ∣ B ∣ + ∣ C ∣ − ∣ A ∩ B ∣ − ∣ B ∩ C ∣ − ∣ C ∩ A ∣ + ∣ A ∩ B ∩ C ∣ |A \cup B \cup C| = |A| + |B| + |C| - |A \cap B| - |B \cap C| - |C \cap A| + |A \cap B \cap C| ABC=A+B+CABBCCA+ABC

其中, ∣ A ∣ |A| A ∣ B ∣ |B| B ∣ C ∣ |C| C分别是集合 A A A B B B C C C的元素个数, ∣ A ∩ B ∣ |A \cap B| AB ∣ B ∩ C ∣ |B \cap C| BC ∣ C ∩ A ∣ |C \cap A| CA分别是集合 A A A B B B B B B C C C C C C A A A的交集的元素个数, ∣ A ∩ B ∩ C ∣ |A \cap B \cap C| ABC是集合 A A A B B B C C C的交集的元素个数。

  1. n n n个集合的容斥原理

A 1 , A 2 , … , A n A_1, A_2, \ldots, A_n A1,A2,,An n n n个集合,则它们的并集 A 1 ∪ A 2 ∪ … ∪ A n A_1 \cup A_2 \cup \ldots \cup A_n A1A2An的元素个数为:

∣ A 1 ∪ A 2 ∪ … ∪ A n ∣ = ∑ i = 1 n ∣ A i ∣ − ∑ 1 ≤ i < j ≤ n ∣ A i ∩ A j ∣ + ∑ 1 ≤ i < j < k ≤ n ∣ A i ∩ A j ∩ A k ∣ − ⋯ + ( − 1 ) n − 1 ∣ A 1 ∩ A 2 ∩ … ∩ A n ∣ |A_1 \cup A_2 \cup \ldots \cup A_n| = \sum_{i=1}^{n} |A_i| - \sum_{1 \leq i < j \leq n} |A_i \cap A_j| + \sum_{1 \leq i < j < k \leq n} |A_i \cap A_j \cap A_k| - \cdots + (-1)^{n-1} |A_1 \cap A_2 \cap \ldots \cap A_n| A1A2An=i=1nAi1i<jnAiAj+1i<j<knAiAjAk+(1)n1A1A2An

其中,求和符号表示对所有可能的集合组合进行求和, ( − 1 ) n − 1 (-1)^{n-1} (1)n1表示当集合的交集数量(即下标集合的大小)为奇数时取正号,为偶数时取负号。

对于 n n n个集合的容斥原理公式来说,最后共有 2 n 2^n 2n项,因为每一项都可以看做是一个组合数,比如 ∑ i = 1 n ∣ A i ∣ \sum_{i=1}^{n}|A_i| i=1nAi中共有是 n n n项,因为相当于 C n 1 C_{n}^{1} Cn1种方案数;对于 ∑ 1 ≤ i < j ≤ n ∣ A i ∩ A j ∣ \sum_{1 \leq i < j \leq n} |A_i \cap A_j| 1i<jnAiAj也同样是这样,相当于 C n 2 C_{n}^{2} Cn2种方案数;那么所有的项目就相当于 C n 1 + C n 2 + C n 3 + ⋯ + C n n = 2 n − 1 C_n^{1} + C_n^{2} + C_n^{3} + \cdots + C_n^{n} = 2^n - 1 Cn1+Cn2+Cn3++Cnn=2n1项,但是其实所有集合都不选也是一项,也就是 C n 0 C_n^0 Cn0,只是没有显式的计算他,因此最开始说共有 2 n 2^n 2n项。

y总还讲了另外一个等式,这个式子表明了容斥原理的核心思想:每个元素只被统计一次
C k 1 − C k 2 + C k 3 − … + ( − 1 ) k + 1 C k k = 1 C_{k}^{1} - C_{k}^{2} + C_{k}^{3} - \ldots + (-1)^{k+1}C_{k}^{k} = 1 Ck1Ck2+Ck3+(1)k+1Ckk=1
这个等式表明,在容斥原理的交替求和公式中,每个元素在所有可能的集合组合(交集)中出现的次数,经过正负交替相加后,总和等于1,从而确保了每个元素在最终的计算中只被统计了一次。

这里再回到题目来看,我们需要统计 1 ∼ n 1 \sim n 1n中至少被一个所给的数整除的数有多少个,也就是说如果一个数能被好几个数整除,也只统计一次,如果我们用暴力做法,需要对 n n n个数都遍历 m m m个数对比,时间复杂度为 O ( n ⋅ m ) O(n \cdot m) O(nm)会超时,但是我们使用容斥原理进行统计的话,就能将时间复杂度降低到 O ( 2 m ) O(2^m) O(2m)

首先,对于每一个给出的质数p_i,都会有一个集合表示所有能被他整除的数,要关注的是这个集合中元素的个数,而不是这些数是哪些数。这个数量我们可以通过 ⌊ n p i ⌋ \lfloor \frac{n}{p_i} \rfloor pin计算得出,同样的对于同时是多个质数的倍数的集合中元素的个数也可以这样计算得到,例如 ⌊ n p i p j ⌋ \lfloor \frac{n}{p_i p_j} \rfloor pipjn的形式,我们可以通过这样的方式处理出所有集合包含元素的个数。

然后,通过对上面n个元素对应的容斥原理计算公式进行观察可以发现,当一个元素被包含在偶数个集合中时,那就应该减去,集合数为奇数时加上,同样用上面的来举例,所有的只被一个质数整除的元素集合的数目 ⌊ n p i ⌋ \lfloor \frac{n}{p_i} \rfloor pin都应该被加到答案 r e s res res中区,所有被两个质数整除的元素集合的数目 ⌊ n p i p j ⌋ \lfloor \frac{n}{p_i p_j} \rfloor pipjn都应该从答案中减去。

至此,题目的思路已经理清了,需要考虑的是代码的逻辑,我们要统计的有两个东西

  1. 所有集合的大小。
  2. 每个集合是能被几个质数整除。

这里就用到了上面对于容斥原理公式包含项数的结果了,一共 2 m − 1 2^m - 1 2m1项,一共有 m m m个质数,那么我们可以通过统计 1 ∼ 2 m 1 \sim 2^m 12m,一共 2 m − 1 2^m - 1 2m1个数代表所有的集合,每个数的二进制表示中的 1 1 1的数量表示这个集合是被几个质数整除的,同时,集合的大小可以在这个过程中同时处理出来,将所有质数存在数组p[N]中,记录一个数t = 1,当第i位为1时,就将t *= p[i],遍历完这个数的二进制的所有位后,通过num = n / t得到该集合的大小,具体的看代码吧

代码

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>using namespace std;typedef long long LL;const int N = 20;int p[N];
int n, m;int main()
{cin >> n >> m;for(int i = 0; i < m; i ++) cin >> p[i];int res = 0;for(int i = 1; i < 1 << m; i ++){int t = 1, cnt = 0;for(int j = 0; j < m; j ++){if(i >> j & 1){if((LL)t * p[j] > n){t = -1;break;}t *= p[j];cnt ++;}}if(t == -1) continue;if(cnt % 2) res += n / t;else res -= n / t;}cout << res << endl;return 0;
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com