一、理论知识
韦恩图(又称文氏图)
(1)两个圆相交求面积
(2)三个圆相交求面积
(3)四个圆相交求面积
上面,我们是用面积来考虑的问题,所以等式左边写的是\(S\),也可以用集合来考虑,以\(3\)个圆为例,那就是
\(|S_1 \cup S_2 \cup S_3| =|S_1|+|S_2|+|S_3|- |S_1\cap S_2| -|S_2\cap S_3| - |S_1\cap S_3| + |S_1\cap S_2 \cap S_3|\)
其中\(||\)代表集合中的元素个数。
(4)规律总结
上面的求解过程,其实是在求 \(C_n^1 - C_n^2+ ... + {(-1)}^{n-1}C_n^n\),
也就理解为从\(n\)个选择1个,减去从\(n\)中选择\(2\)个,加上从\(n\)中选择\(3\)个,减去从\(n\)中减去\(4\)个...,也可以记为奇数个元素的集合是加,偶数个的(指相交)的集合是减。
(5)经典例题
容斥原理有个经典题目:一个班每个人都有自己喜欢的科目,有\(20\)人喜欢数学,\(10\)人喜欢语文,\(11\)人喜欢英语,其中\(3\)人同时喜欢数学语文,\(3\)人同时喜欢语文英语,\(4\)人同时喜欢数学英语,\(2\)人都喜欢,问全班有多少人?
根据容斥原理,就是\(\large S=S_1+S_2+S_3- S_1\cap S_2 -S_2\cap S_3 - S_1\cap S_3 + S_1\cap S_2 \cap S_3\)
班级人数=\(20+10+11-3-3-4+2\)
二、算法思路
-
数学表达式
比如三个质数是\(2,3,5\),那么:\(S_2\)就代表\(2\)的倍数集合,\(S_3\)就代表\(3\)的倍数集合,\(S_5\)就代表\(5\)的倍数集合,至少能被其中一个质数整除的就是:
\(|S_2 \cup S_3 \cup S_5| = |S_2| + |S_3| +|S_5| -|S_2 \cap S_3| -|S_3 \cap S_5| -|S_2 \cap S_5|+|S_2 \cap S_3 \cap S_5|\) -
如何计算\(|S_p|\)这样的表达式数值
\(|S_p|\)就是计算小于等于\(n\)中能整除掉\(p\)的数字个数。它就是等于\(\lfloor \frac{n}{p} \rfloor\),而\(C++\)在整数运算除法时,默认就是下取整,这点就不用再特意处理了。 -
如何计算\(|S_2 \cap S_3|\)这样的的表达式值
题目给定的\(p_i\)都是质数,所以能被\(2\)整除,也能被\(3\)整除的数,肯定是能被\(6\)整除的数,所以就是 \(|S_6|=|S_2 \cap S_3|\),这样,问题就转化成了问题\(|S_p|\),也就会求解了! -
怎么把这些子项目都罗列出来
其实罗列的是\(p[i]\)的所有组合方式!举个栗子: \(\{2\},\{3\},\{5\},\{2,3\},\{3,5\},\{2,5\},\{2,3,5\}\)共\(7\)种,也就是\(2^3-1=7\)种。
这个是常用的小技巧了,二进制模拟
三、C++ 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 20;
int p[N]; //质数数组
int n; //整数n,表示1~n之间的整数
int m; //m个不同的质数
int res; //结果
int main() {
//优化输入
ios::sync_with_stdio(false);
cin >> n >> m;
//读入m个质数
for (int i = 0; i < m; i++) cin >> p[i];
//二进制来模拟m个数字的 2^m种选法,注意,不能全不选,所以i=1开始
//举个栗子,比如 2,3,5共3个数字,就是 001~111共7种选法
for (int i = 1; i < 1 << m; i++) {
int t = 1; //t代表当前选法中,选中质数的乘积,初始值是1
int cnt = 0; //cnt当前选法里面包含几个1,奇数个1是+,偶数个1是-,所以需要记录
bool flag = true; //本轮结果是否有效
for (int j = 0; j < m; j++) //遍历二进制的每一位
if (i >> j & 1) { //判断j位二进制是不是1
//如果质数乘积大于n了,就不用再继续算了
if ((LL) t * p[j] > n) {
flag = false;
break;
}
t *= p[j]; //质数相乘
cnt++;
}
//容斥原理公式
if (flag) {
if (cnt % 2) res += n / t; //奇数项加,C++的整数除法默认是下取整,不用再关心取整问题
else res -= n / t; //偶数项减
}
}
//输出答案
printf("%d", res);
return 0;
}