简介
推荐题解:https://www.acwing.com/solution/content/126553/
画了图,清晰易懂,懒得打字了。
总之就是以下公式:
S
=
S
1
+
S
2
+
S
3
−
S
1
∩
S
2
−
S
1
∩
S
3
−
S
2
∩
S
3
+
S
1
∩
S
2
∩
S
3
\begin{align*} S = & S_1 + S_2 + S_3 \\ & - S_1 \cap S_2 - S_1 \cap S_3 - S_2 \cap S_3 \\ & + S_1 \cap S_2 \cap S_3 \end{align*}
S=S1+S2+S3−S1∩S2−S1∩S3−S2∩S3+S1∩S2∩S3
我们可以把这个式子推导到
n
n
n 维,奇加偶减。
AcWing 890. 能被整除的数
题目链接:https://www.acwing.com/activity/content/problem/content/960/
思路解析
筛出一个数的倍数,两个数的倍数 … n n n 个数的倍数,这就抽象成了 n n n 个集合的问题了。
那么如何表示选取哪几个集合(质数)呢?
- 从 1 1 1 开始枚举到 n n n,将每个数看成二进制形式,如果说第 k k k 位是 1 1 1,那么就代表选第 k k k 个集合,反之不选。
如何知道被整除的数的个数?公式: n / p n / p n/p 下取整即可。
最后判断是奇数个还是偶数个,这个判断利用了二进制的一个性质:除了 2 0 2^0 20,其他所有位的和都是 2 2 2 的整数倍,所以说看是否为奇数就看它二进制最后一位是否为 1 1 1。
CODE
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll; // 定义长整型别名为ll
const int N = 20; // 定义常量N为20
int n, m; // 定义整型变量n和m
int p[N]; // 定义整型数组p,大小为N
int main(){
scanf("%d%d", &n, &m); // 从输入中读取两个整数n和m
for(int i = 0; i < m; ++i) scanf("%d", &p[i]); // 从输入中读取m个整数到数组p中
int res = 0; // 初始化结果为0
for(int i = 1; i < (1 << m); ++i){ // 遍历所有的子集
int s = 0, t = 1; // 初始化s和t
for(int j = 0; j < m; ++j){ // 遍历每一位
if(i >> j & 1){ // 如果第j位为1
if((ll)t * p[j] > n){ // 如果t乘以p[j]大于n
t = -1; // 将t设置为-1
break; // 跳出循环
}
t *= p[j]; // 更新t
s++; // 增加s
}
}
if(t == -1) continue; // 如果t为-1,跳过当前循环
if(s & 1) res += n / t; // 如果s为奇数,增加n/t到res
else res -= n / t; // 否则,从res中减去n/t
}
cout << res << endl; // 输出结果
}