文章目录
1. 容斥原理+数学
重点: 容斥原理
暴力枚举,最坏需要针对每个数 n n n,枚举完所有的质数 m m m 查看能否乘除。则时间复杂度为 O ( n m ) O(nm) O(nm),超时。
以样例,采用容斥原理进行答案求解
思路:
- 定义集合 S 2 = 2 、 4 、 6 、 8 、 10 S_2=2、4、6、8、10 S2=2、4、6、8、10, S 3 = 3 、 6 、 9 S_3=3、6、9 S3=3、6、9,其中 S 2 、 S 3 S_2、S_3 S2、S3 中集合元素是所能被 2,3 整除的元素。
- 那么有, S 2 ∩ S 3 = 6 S_2 \cap S_3 = 6 S2∩S3=6
- ∣ S 2 ∪ S 3 ∣ = ∣ S 2 ∣ + ∣ S 3 ∣ − ∣ S 2 ∩ S 3 ∣ = 5 + 3 − 1 = 7 \mid S_2 \cup S_3 \mid = \mid S_2\mid + \mid S_3 \mid - \mid S_2 \cap S_3 \mid = 5 + 3 - 1 = 7 ∣S2∪S3∣=∣S2∣+∣S3∣−∣S2∩S3∣=5+3−1=7。即能够发现,我们只要处理得到各个质数对应集合中的元素个数就能算出答案。
- 求解 1 ∼ n 1\sim n 1∼n 中 p p p 的倍数的个数,为 ⌊ n p ⌋ \lfloor \frac n p\rfloor ⌊pn⌋。且由于给定都是质数,那么 ∣ S 2 ∩ S 3 ∣ = n 2 × 3 \mid S_2\cap S_3\mid = \frac n {2 \times 3} ∣S2∩S3∣=2×3n,可推广至 k k k 个素数集合的交,即为其最小公倍数。
- 算 k k k 个素数所构成的集合的元素个数其实是需要计算 k k k 次乘法的,即 n p 1 × p 2 × . . . × p k \frac n {p_1 \times p_2 \times ...\times p_k } p1×p2×...×pkn,那么计算每个集合的时间复杂度就是 O ( k ) O(k) O(k),我们总共有 2 m 2^m 2m 个集合(这里就是 C m 1 + C m 2 . . . + C m m = 2 m C_m^1+C_m^2...+C_m^m=2^m Cm1+Cm2...+Cmm=2m,是一个简单的组合恒等式),那么总共的时间复杂度为 O ( 2 m k ) = O ( 2 m m ) = 2 20 = 1 0 6 O(2^mk) = O(2^mm)=2^{20}= 10^6 O(2mk)=O(2mm)=220=106,时间复杂度满足要求
- 至此,本题即为已知 m m m 个质数,为 p 1 , p 2 , . . . , p m p_1,p_2,...,p_m p1,p2,...,pm,求解 ∣ S p 1 ∪ S p 2 ∪ . . . ∪ S p m ∣ = ∣ S p 1 ∣ + ∣ S p 2 ∣ + . . . + ∣ S p m ∣ − ∣ S p 1 ∩ S p 2 ∣ − . . . + ∣ S p 1 ∩ S p 2 ∩ S p 3 ∣ . . . \mid S_{p_1}\cup S_{p_2}\cup...\cup S_{p_m}\mid = \mid S_{p_1}\mid +\mid S_{p_2}\mid +...+\mid S_{p_m}\mid - \mid S_{p_1}\cap S_{p_2}\mid-...+\mid S_{p_1}\cap S_{p_2} \cap S_{p_3}\mid... ∣Sp1∪Sp2∪...∪Spm∣=∣Sp1∣+∣Sp2∣+...+∣Spm∣−∣Sp1∩Sp2∣−...+∣Sp1∩Sp2∩Sp3∣...
二进制思想枚举所有情况:
由于我们处理集合的数量比较多,有
2
m
2^m
2m 个集合。如何枚举各个集合的交集是个问题,可以采用 dfs
暴力枚举。在此采用 2 进制的思想来判定该集合是否被选,共有
2
m
2^m
2m 个情况,
m
m
m 个质数,
m
m
m 个二进制位,其中二进制位为 1 的时候就代表选这个集合,不为 1 则表示不选这个集合。这样就可以快速枚举出所有的情况,针对性处理即可。
模板代码:
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 20;
int n, m;
int p[N];
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) {
++cnt;
if ((LL)t * p[j] > n) {
t = -1;
break;
}
t *= p[j];
}
}
if (t != -1) {
if (cnt % 2) res += n / t;
else res -= n / t;
}
}
cout << res << endl;
return 0;
}