[数学] 能被整除的数(容斥原理+二进制枚举)

文章目录

1. 容斥原理+数学

890. 能被整除的数

在这里插入图片描述
重点: 容斥原理

暴力枚举,最坏需要针对每个数 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=246810 S 3 = 3 、 6 、 9 S_3=3、6、9 S3=369,其中 S 2 、 S 3 S_2、S_3 S2S3 中集合元素是所能被 2,3 整除的元素。
  • 那么有, S 2 ∩ S 3 = 6 S_2 \cap S_3 = 6 S2S3=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 S2S3=S2+S3S2S3=5+31=7。即能够发现,我们只要处理得到各个质数对应集合中的元素个数就能算出答案。
  • 求解 1 ∼ n 1\sim n 1n 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} S2S3=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 p1p2...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... Sp1Sp2...Spm=Sp1+Sp2+...+SpmSp1Sp2...+Sp1Sp2Sp3...

二进制思想枚举所有情况:

由于我们处理集合的数量比较多,有 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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值