能被整除的数
题目描述
样例解释
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 2n−1项。即从n个集合中选出若干个集合,对于每个集合,有两种状态,选它或者不选它,因此有n个集合,于是就有 2 n 2^n 2n种状态,排除一个集合动不选的这个状态,还剩下 2 n − 1 2^n-1 2n−1个状态。 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 Ck1−Ck2+Ck3−Ck4⋯+(−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=2n−1
如何理解 ∣ ⋃ 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=1⋃nSi∣=i=1∑n∣Si∣−1≤i<j≤n∑∣Si⋂Sj∣+1≤i<j<k≤n∑∣Si⋂Sj⋂Sk∣ ⋯ + ( − 1 ) n + 1 ∣ S 1 ⋂ S 2 ⋯ ⋂ S n ∣ \cdots +(-1)^{n+1}|S_1\bigcap S_2\cdots \bigcap S_n| ⋯+(−1)n+1∣S1⋂S2⋯⋂Sn∣呢?
核心思路
设 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=S1⋃S2={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 S1⋂S2这个集合的大小呢?这里因为题目已经说了 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 ∣S1⋂S2∣=p1∗p2n=2∗310=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} p1∗p2∗⋯∗pxn
如何用代码表示每个集合的状态?
有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} p1∗p2∗p4n。 因为集合个数是3个,根据公式,前面的系数为 ( − 1 ) 3 − 1 = 1 (-1)^{3-1}=1 (−1)3−1=1。所以到当前这个状态时,应该是 r e s + = n p 1 ∗ p 2 ∗ p 4 res+=\dfrac {n}{p_1*p_2*p_4} res+=p1∗p2∗p4n。这样用二进制0000到1111就可以表示m=4时的集合状态了。
由于至少要包含一个集合,因此枚举时是从0001到 2 m − 1 2^m-1 2m−1,即区间是 [ 1 , 2 m − 1 ] [1,2^m-1] [1,2m−1]。
如何理解 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=p1∗p2∗p3=2∗3∗5=30。
如何理解 t ∗ p [ j ] > n t*p[j]>n t∗p[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 t∗p[j]=2∗3∗5=30,但是24小于30,1到24中并不存在能够被30整除的元素,因为30已经超过n这个数本身了,因此需要提前break。
由容斥原理可知,总共有 2 m − 1 2^m-1 2m−1项,比如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| ∣S1⋃S2∣=∣S1∣−∣S1⋂S2∣+∣S2∣,当i=1时,说明在枚举第一项,即 ∣ S 1 ∣ |S_1| ∣S1∣,当i=2时,说明在枚举第二项,即 ∣ S 1 ⋂ S 2 ∣ |S_1\bigcap S_2| ∣S1⋂S2∣,当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| ∣S1∣、∣S2∣、⋯、∣Sn∣了。这正对应了容斥原理中等式右边的第一项。
代码
#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;
}