题目传送门

一、理论知识

韦恩图(又称文氏图)

(1)两个圆相交求面积

AcWing 890. 能被整除的数
$\large S=S_1+S_2-S_1\cap S_2$

(2)三个圆相交求面积

AcWing 890. 能被整除的数
$\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$

(3)四个圆相交求面积

AcWing 890. 能被整除的数
$\large S=S_1+S_2 + S_3 + S_4 -S_1\cap S_2 -S_1\cap S_3 -S_1\cap S_4 - S_2\cap S_3 -S_2\cap S_4 -S_3\cap S_4 + S_1\cap S_2 \cap S_3 + S_1\cap S_2 \cap S_4+ S_2\cap S_3 \cap S_4 ++ S_1\cap S_3 \cap S_4 - S_1 \cap S_2 \cap S_3 \cap S_4$

上面,我们是用面积来考虑的问题,所以等式左边写的是\(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\)种。
    这个是常用的小技巧了,二进制模拟
    AcWing 890. 能被整除的数

三、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;
}

相关文章:

  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-07-07
  • 2021-05-30
  • 2021-09-14
  • 2022-12-23
  • 2022-12-23
猜你喜欢
  • 2022-12-23
  • 2021-12-25
  • 2022-12-23
  • 2021-10-17
  • 2022-12-23
  • 2021-09-17
  • 2022-12-23
相关资源
相似解决方案