AcWing 890. 能被整除的数

AcWing 890. 能被整除的数

一、题目描述

给定一个整数 nm 个不同的质数 p1,p2,,pm

请你求出 1n 中能被 p1,p2,,pm 中的 至少一个数整除的整数 有多少个。

输入格式
第一行包含整数 nm

第二行包含 m 个质数。

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

数据范围
1m16,1n,pi109

输入样例:

10 2
2 3

输出样例:

7

二、理论知识

韦恩图(又称文氏图)

(1)两个圆相交求面积

S=S1+S2S1S2

(2)三个圆相交求面积

S=S1+S2+S3S1S2S2S3S1S3+S1S2S3

(3)四个圆相交求面积

S=S1+S2+S3+S4S1S2S1S3S1S4S2S3S2S4S3S4+S1S2S3+S1S2S4+S2S3S4++S1S3S4S1S2S3S4

上面,我们是用面积来考虑的问题,所以等式左边写的是S,也可以用集合来考虑,以3个圆为例,那就是
|S1S2S3|=|S1|+|S2|+|S3||S1S2||S2S3||S1S3|+|S1S2S3|

其中||代表集合中的元素个数

(4)规律总结

上面的求解过程,其实是在求 Cn1Cn2+...+(1)n1Cnn
也就理解为从n个选择1个,减去从n中选择2个,加上从n中选择3个,减去从n中减去4个...,也可以记为奇数个元素的集合是加,偶数个的(指相交)的集合是减。

(5)经典例题

容斥原理有个经典题目:一个班每个人都有自己喜欢的科目:

  • 20人喜欢数学,10人喜欢语文,11人喜欢英语;
  • 3人同时喜欢数学语文,3人同时喜欢语文英语,4人同时喜欢数学英语
  • 2人都喜欢
    问全班有多少人?

根据容斥原理,就是S=S1+S2+S3S1S2S2S3S1S3+S1S2S3

班级人数=20+10+11334+2=33

三、本题思路

数学表达式
比如三个质数是235S2代表2的倍数集合,S3代表3的倍数集合,S5代表5的倍数集合,至少能被其中一个质数整除的总个数就是:

|S2S3S5|=|S2|+|S3|+|S5||S2S3||S3S5||S2S5|+|S2S3S5|

解释:上面表示式中|S2|代表的是: 2的倍数集合的数字个数

  • Q1:如何计算|Sp|这样的表达式数值?
    |Sp|:计算小于等于n中能整除掉p的数字个数 np,C++默认就是下取整,不用再特殊处理。

  • Q2:如何计算|S2S3|这样的表达式值?
    题目给定的pi都是质数,所以能被2整除,也能被3整除的数,肯定是能被6整除的数,所以就是 |S6|=|S2S3|,这样,问题就转化成了问题|Sp|,也就会求解了!

  • Q3:怎么把这些子项目都罗列出来?
    其实罗列的是p[i]的所有组合方式!举个栗子: {2},{3},{5},{2,3},{3,5},{2,5},{2,3,5}7种,也就是231=7种。

常用技巧:二进制数位枚举
QQ截图20210308155806.png

举个栗子:

#include <bits/stdc++.h>

using namespace std;
#define int long long
#define endl "\n"

int n, m;      // n:质数个数,m:1~m的数字中有多少个可以被质数序列中至少一个整数整除。
               // 注意:代码里的n,m与模板题目中的含义相反!一定要注意!!!!!!!!!!!!
vector<int> p; // 质数数组

signed main() {
    cin >> m >> n; // 与m互质,n个质数!

    // 读入n个质数,为了使用vector<int>,读入时确实不太方便
    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        p.push_back(x);
    }

    // ① 枚举从1到 2^n-1,每个数字,代表一种状态,每个状态代表一种质数的挑选办法
    // 当然,这些整数值的乘积可能大于n,大于的没用,只要小于等于n的
    int s = 0;
    for (int i = 1; i < 1 << p.size(); i++) {
        int t = 1, cnt = 0; // 累乘积,质因子个数
        // ② 在对应的整数值确定后,枚举此数值的每一个数位
        for (int j = 0; j < p.size(); j++)
            if (i >> j & 1) {       // ③判断当前数位是不是1,是1表示当前数位选中
                if (t * p[j] > m) { // 乘积不能超过最大值m,控制在[1~m]范围内
                    t = 0;          // s=0代表本次挑选的组合失败,无效
                    break;          // 由于i是由小到大遍历的,前面的都无效了,后面的肯定更大,更无效,不用继续了
                }
                cnt++;     // 选择的质因子个数
                t *= p[j]; // 累乘积
            }
        if (t) {            // 超过范围的,s=0,所以,现在代表只讨论在范围内的
            if (cnt & 1)    // 质数因子数量,奇数加
                s += m / t; // 引理内容,代表m里面有多少个这个数字s的倍数
            else            // 偶数减
                s -= m / t;
        }
    }
    cout << s << endl;
}
posted @   糖豆爸爸  阅读(443)  评论(0编辑  收藏  举报
编辑推荐:
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
历史上的今天:
2019-10-10 代码战争
2017-10-10 云平台服务器应急检查步骤
2017-10-10 解决Tomcat因Redis加载慢而启动失败的问题
2017-10-10 检测磁盘挂载的方法
Live2D
点击右上角即可分享
微信分享提示