班里有10个人喜欢数学,15个喜欢语文,21个喜欢编程,一共有多少学生呢
用A, B, C表示人数
总数等于|A∪B∪C|,直接相加肯定是不对的
有一些重复了,所以扣掉|A∩B|,|A∩B|,|A∩C|,但是又多扣了一小部分,再加上|A∩B∩C|
即|A∪B∪C| = |A| + |B| + |C| - |A∩B| - |A∩B| - |A∩C| + |A∩B∩C|
所以,一般的对任意多个集合都可以列出这样一个等式,即左边是元素个数,右边可看做是集合的非空子集,每个组合都是若干集的交集
而加还是减取决于元素个数,奇加偶减,即奇数个元素是加,偶数个元素是减
例如求1~m中与n互为素数的数的个数
可先由唯一分解定理,将n的素因子求出
如2是n的素因子,求1~m中与n互素的数,m/2即可得到,1~m中与n中没有公共因子2的数的个数,也就是一部分与n不互素的数的个数
所以n的素因子即可看做元素的前驱,再用m除以素因子的所有非空子集即可得到真正的元素,最后根据奇加偶减计算可得到所有不互素的数,用m减去即可得到互素的数
typedef long long LL;
void get_fac(int n){
ans = 0;
for(int i = 2; i*i <= n; i++){
if(n%i == 0){
num[ans++] = i;
while(n%i == 0) n /= i;
}
}
if(n > 1) num[ans++] = n;
}
然后可由三种方式遍历出元素的非空子集
首先是数组实现
LL exc(LL A){
LL cnt, t, k;
cnt = t = 0;
for(int i = 0; i < ans; i++){
k = t;
fac[t++] = num[i];
for(int j = 0; j < k; j++) fac[t++] = -1*fac[j]*num[i];//可遍历出所有非空子集并根据奇加偶减储存
}
for(LL i = 0; i < t; i++) cnt += A/fac[i];//计算所有不互素的数,已处理过奇加偶减
return A-cnt;
}
然后可以根据二进制实现
可用二进制表示n的素因子,如1010111,第i位是1表示当前组合包含此因子,0表示不包含,如果n有x个因子,从1加到1<<x再由按位与运算判断每个位是1还是0,即可根据二进制得出所有非空子集,再处理一下奇加偶减,即有当前组合包含奇数或偶数个因子,即奇数或偶数个1
LL exc(LL A){
LL cnt = 0;
for(LL i = 1; i < LL(1<<ans); i++){
LL bit, cur;
bit = 0; cur = 1;
for(LL j = 0; j < ans; j++){
if(LL(1<<j) & i){
bit++;
cur *= num[j];
}
}
cur = A/cur;
if(bit&1) cnt += cur;
else cnt -= cur;
}
return A-cnt;
}
上边两种原理大致一样,最后,是代码最简单的递归实现,与前边两种稍有不同
如素因子有2,3,5,7
则每次求 cnt += n/num[i] - exc(n/num[i], i+1) 为了求与2不互素减去与2*3不互素重复,加上和2*3*5不互素又重复的部分,根据递归的性质, 减法依次递归可得奇加偶减
LL exc(LL n, int i){
LL cnt = 0;
for(; i < ans; i++) cnt += n/num[i] - exc(n/num[i], i+1);
return cnt;
}
LL sum = b-exc(b, 0);
函数并没有直接求出互素的数,而是求出了不互素的数,要在函数外减去才行