能被整除的数

能被整除的数

题目描述

在这里插入图片描述


样例解释

1到10的元素有{1,2,3,4,5,6,7,8,9,10},能被2整除的元素集合为{2,4,6,8,10}为5个,能被3整除的元素集合为{3、6、9}为3个,由于6重复被多算了一次,因此总的次数就是5+3-1=7.


前置知识

容斥原理

在这里插入图片描述

容斥原理它总共有 2 n − 1 2^n-1 2n1项。即从n个集合中选出若干个集合,对于每个集合,有两种状态,选它或者不选它,因此有n个集合,于是就有 2 n 2^n 2n种状态,排除一个集合动不选的这个状态,还剩下 2 n − 1 2^n-1 2n1个状态。 C k 1 − C k 2 + C k 3 − C k 4 ⋯ + ( − 1 ) k + 1 C k k C_k^{1}-C_k^{2}+C_k^3-C_k^4\cdots +(-1)^{k+1}C_k^k Ck1Ck2+Ck3Ck4+(1)k+1Ckk

由二项式定理: C n 0 + C n 1 + C n 2 + ⋯ + C n n = 2 n C_n^0+C_n^1+C_n^2+\cdots +C_n^n=2^n Cn0+Cn1+Cn2++Cnn=2n,于是有 C n 1 + C n 2 + ⋯ + C n n = 2 n − 1 C_n^1+C_n^2+\cdots +C_n^n=2^n-1 Cn1+Cn2++Cnn=2n1

如何理解 ∣ ⋃ i = 1 n S i ∣ = ∑ i = 1 n ∣ S i ∣ − ∑ 1 ≤ i < j ≤ n ∣ S i ⋂ S j ∣ + ∑ 1 ≤ i < j < k ≤ n ∣ S i ⋂ S j ⋂ S k ∣ |\bigcup \limits _{i=1}^nS_i|=\sum \limits _{i=1}^n|S_i|-\sum \limits _{1\leq i<j\leq n}|S_i\bigcap S_j|+\sum \limits _{1\leq i<j<k\leq n}|S_i\bigcap S_j\bigcap S_k| i=1nSi=i=1nSi1i<jnSiSj+1i<j<knSiSjSk ⋯ + ( − 1 ) n + 1 ∣ S 1 ⋂ S 2 ⋯ ⋂ S n ∣ \cdots +(-1)^{n+1}|S_1\bigcap S_2\cdots \bigcap S_n| +(1)n+1S1S2Sn呢?

在这里插入图片描述

在这里插入图片描述


核心思路

S i S_i Si表示1到n中能整除 p i p_i pi的集合,那么根据容斥原理, 所有数的个数为各个集合的并集。我们设 ∣ S ∣ |S| S表示集合 S S S所含有的元素个数

以题目样例解释:

设集合 S 1 S_1 S1表示能被质数2整除的集合,则 S 1 S_1 S1={2,4,6,8,10}

设集合 S 2 S_2 S2表示能被质数3整除的集合,则 S 2 S_2 S2={3,6,9}

得到的结果就是 S 1 S_1 S1 S 2 S_2 S2的并集,即 S = S 1 ⋃ S 2 S=S_1 \bigcup S_2 S=S1S2={2,3,4,6,8,9,10},那么答案就是集合 S S S所含有的元素个数,即为7.

每个集合实际上并不需要知道具体元素是什么,只要知道这个集合的大小即可。比如集合 S 1 S_1 S1表示能被质数2整除的集合,那么我们怎么知道1到n中能被 p i p_i pi=2整除的元素的个数呢?其实有个公式,就是 n / p i n/p_i n/pi,只要算出了 n / p i n/p_i n/pi也就是算出了1到n中能被 p i p_i pi整除的元素的个数了,也就是知道了这个集合 S i S_i Si的大小了。即 ∣ S i ∣ = n / p i |S_i|=n/p_i Si=n/pi,向下取整。比如题目中 ∣ S 1 ∣ = 10 / 2 = 5 |S_1|=10/2=5 S1=10/2=5 ∣ S 2 ∣ = 10 / 3 = 3 |S_2|=10/3=3 S2=10/3=3

交集的大小如何确定呢?

比如说 p i = 2 p_i=2 pi=2 p i = 3 p_i=3 pi=3,那么1到n中可能有同时能被2和3整除的,比如元素6。也就是如何确定 S 1 ⋂ S 2 S_1 \bigcap S_2 S1S2这个集合的大小呢?这里因为题目已经说了 p i p_i pi是质数,那么这些质数的乘积就是他们的最小公倍数,n除这个最小公倍数就是交集的大小。举个例子吧:比如n=24,有质数 p i p_i pi是2,3。那么1到24中能同时被2和3整除的元素有6,12,18,24共有4个,用公式计算就是:先算出2和3的最小公倍数,是6。那么交集的大小为24/6=4。在这个题目中就是 ∣ S 1 ⋂ S 2 ∣ = n p 1 ∗ p 2 = 10 2 ∗ 3 = 1 |S_1 \bigcap S_2|=\dfrac {n}{p_1*p_2}=\dfrac {10}{2*3}=1 S1S2=p1p2n=2310=1

依次类推, p 1 , p 2 , ⋯   , p x p_1,p_2,\cdots ,p_x p1,p2,,px的交集大小就是 n p 1 ∗ p 2 ∗ ⋯ ∗ p x \dfrac {n}{p_1*p_2*\cdots *p_x} p1p2pxn

如何用代码表示每个集合的状态?

有m个质数,每个质数都确定了一种集合。这里可以考虑用二进制。以m = 4为例,每个二进制位可以表示选和不选该集合的状态,用1表示选了该集合,用0表示不选该集合,m=4因此需要4个二进制位。比如1011则表示选择了 S 1 , S 2 , S 4 S_1,S_2,S_4 S1,S2,S4这三个集合。其中 S 1 S_1 S1集合表示1到n中能整除 p 1 p_1 p1的所有元素的集合, S 2 S_2 S2集合表示1到n中能整除 p 2 p_2 p2的所有元素的集合, S 4 S_4 S4集合表示1到n中能整除 p 4 p_4 p4的所有元素的集合,故这个集合中元素的个数为 n p 1 ∗ p 2 ∗ p 4 \dfrac {n}{p_1*p_2*p_4} p1p2p4n。 因为集合个数是3个,根据公式,前面的系数为 ( − 1 ) 3 − 1 = 1 (-1)^{3-1}=1 (1)31=1。所以到当前这个状态时,应该是 r e s + = n p 1 ∗ p 2 ∗ p 4 res+=\dfrac {n}{p_1*p_2*p_4} res+=p1p2p4n。这样用二进制0000到1111就可以表示m=4时的集合状态了。

由于至少要包含一个集合,因此枚举时是从0001到 2 m − 1 2^m-1 2m1,即区间是 [ 1 , 2 m − 1 ] [1,2^m-1] [1,2m1]

如何理解 t ∗ = p [ j ] t*=p[j] t=p[j]呢?

比如 p 1 = 2 , p 2 = 3 , p 3 = 5 p_1=2,p_2=3,p_3=5 p1=2,p2=3,p3=5,那么 t = p 1 ∗ p 2 ∗ p 3 = 2 ∗ 3 ∗ 5 = 30 t=p_1*p_2*p_3=2*3*5=30 t=p1p2p3=235=30

如何理解 t ∗ p [ j ] > n t*p[j]>n tp[j]>n呢?

比如 p 1 = 2 , p 2 = 3 , p 3 = 5 p_1=2,p_2=3,p_3=5 p1=2,p2=3,p3=5,n=24,那么 t ∗ p [ j ] = 2 ∗ 3 ∗ 5 = 30 t*p[j]=2*3*5=30 tp[j]=235=30,但是24小于30,1到24中并不存在能够被30整除的元素,因为30已经超过n这个数本身了,因此需要提前break。

在这里插入图片描述

由容斥原理可知,总共有 2 m − 1 2^m-1 2m1项,比如m=2,那么就有3项,则 ∣ S 1 ⋃ S 2 ∣ = ∣ S 1 ∣ − ∣ S 1 ⋂ S 2 ∣ + ∣ S 2 ∣ |S_1 \bigcup S_2|=|S_1|-|S_1\bigcap S_2|+|S_2| S1S2=S1S1S2+S2,当i=1时,说明在枚举第一项,即 ∣ S 1 ∣ |S_1| S1,当i=2时,说明在枚举第二项,即 ∣ S 1 ⋂ S 2 ∣ |S_1\bigcap S_2| S1S2,当i=3时,说明在枚举第三项,即 ∣ S 3 ∣ |S_3| S3

对于 ∣ S 1 ∣ + ∣ S 2 ∣ + ⋯ + ∣ S n ∣ |S_1|+|S_2|+\cdots +|S_n| S1+S2++Sn拆开来看的话,就是 ∣ S 1 ∣ |S_1| S1,只选1个,奇数,所以前面符号是正+;对于 ∣ S 2 ∣ |S_2| S2,只选1个,奇数,所以前面符号是正+;对于 ∣ S n ∣ |S_n| Sn,只选1个,奇数,所以前面符号是正+

也就是说我们也统计了 ∣ S 1 ∣ 、 ∣ S 2 ∣ 、 ⋯ 、 ∣ S n ∣ |S_1|、|S_2|、\cdots 、|S_n| S1S2Sn了。这正对应了容斥原理中等式右边的第一项。


代码

#include<iostream>
using namespace std;
typedef long long LL;
const int N=20;
int n,m;
int p[N];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=0;i<m;i++)
        scanf("%d",&p[i]);
    int res=0;  //表示满足条件的整数的个数。
    //由于至少要包含一个集合,因此是从00...001枚举到2^m-1
    //即枚举区间是[1,2^m-1]
    for(int i=1;i<(1<<m);i++)
    {
        int t=1;     //选中集合对应质数的乘积
        int cnt=0;  //选中的集合数量
        //枚举当前状态的每一位
        for(int j=0;j<m;j++)    //判断这m个二进制位
        {
            if((i>>j&1))//如果该二进制位是1
            {
                //乘积大于n, 则n/t = 0, 跳出这轮循环
                if((LL)t*p[j]>n)
                {
                    t=-1;
                    break;
                }
                cnt++;       //有一个1,集合数量+1
                t*=p[j];
            }
        }
        if(t==-1)
            continue;
        //选中奇数个集合, 由容斥原理公式可知,前面符号是正号+, n/t为当前这种状态的集合数量
        if(cnt&1)
            res+=n/t;
        //选中偶数个集合, 由容斥原理公式可知,前面符号是负号-, n/t为当前这种状态的集合数量
        else
            res-=n/t;
    }
    printf("%d\n",res);
    return 0;
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

卷心菜不卷Iris

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值