给定a[1],a[2],a[3],...a[m],求1到n的整数中至少能整除a中一个元素的数有几个?
限制条件:
1<=n<=10^9
1<=m<=15
样例:
输入
n=100,m=2
a={2,3}
输出
67(2的倍数有50个,3的倍数有33个,6的倍数有16个,因此50+33-16=67个)
思路:如果对于1到n中的每个数都判断是否满足条件,那么复杂度是O(n*m),无法在时间限制之内得出答案。由于m的范围比较小,我们可以试着对这一条件加以利用。
首先我们考虑一下m=1的情况,答案是n/a[1];当m=2时,n/a[1]+n/a[2]-n/lcm(a[1],a[2]);依次类推,就可以发现对于一般情况的计算方法。
下面我们就来试着写出一般情况的计算公式,假设A[i](1<=i<=m)是X的一些子集,我们要求|A[i]|。例如在上面的问题里,就可以看成A[i]={n|n可以整除a[i]}。此时,有如下等式成立:|∪A[i]|=∑|A[i]|(1<=i<=m)-∑|A[i]∩A[j]|(1<=i<j<=m)+∑|A[i]∩A[j]∩A[k]|(1<=i<j<k<=m)-...+(-1)^(m+1)∑|A[1]∩A[2]∩...∩A[m]|
这个式子被称作容斥原理。一般在集合的个数m比较小,并且|A[i]∩A[j]∩...∩A[k]|这些项容易计算时适合使用容斥原理。
时间复杂度:假设计算一个|A[i]∩A[j]∩...∩A[k]|需要花费O(f)的时间,那么总共就需要花费O(2^m*f)的时间
代码:
typedef long long ll;
int a[max_m];
int n,m;
int gcd(int a,int b)
{
if(b==0) return a;
return gcd(b,a%b);
}
void solve(){
ll res=0;
for(int i=1;i<(1<<m);i++)
{
int num=0;
for(int j=1;j!=0;j>>=1)
num+=j&1;//i的二进制表示中1的数量
ll lcm=1;
for(int j=0;j<m;j++)
{
if(i>>j&1)
{
lcm=lcm/gcd(lcm,a[j])*a[j];
//如果lcm大于n,则n/lcm=0.因此在溢出之前break。
if(lcm>n)
break;
}
}
if(num%2==0)
res-=n/lcm;
else
res+=n/lcm;
}
printf("%d\n",res);
}