容斥原理

来源:互联网 发布:什么社交软件好 编辑:程序博客网 时间:2024/05/18 01:55

班里有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);

函数并没有直接求出互素的数,而是求出了不互素的数,要在函数外减去才行

0 0
原创粉丝点击