容斥原理

班里有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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值