jr的数学の的奇妙冒险-3&容斥原理

更新日志:

Upd2024-11-2:『更新了多个集合的容斥原理』.

常用符号&运用:

容斥原理中,有这几种常用的符号:

  • 如果想表示一个集合的元素数量,那么我们用\(| \mathbb{\mathbb{A} } |\) 来表示,记作A的基数.

  • 定义 \(\mathbb{\mathbb{A} } \cup \mathbb{\mathbb{B} }\) 表示A与B的并集,即在A中有或在B中有的元素.

  • 定义 \(\mathbb{\mathbb{A} } \cap \mathbb{\mathbb{B} }\) 表示A与B的交集,即在A中有同时在B中有的元素.

这些符号理解起来并不麻烦,但正是这些符号不断的组合起来,才构成了一道道困难的题目.

没错!学习容斥原理不需要什么高深的基础,只要有可以思考的脑子就可以.

那么现在我们来做几道题练练手:

现在有一千个数1~1000,请求出不能被五整除不能被六整除的数的数量.

给几分钟思考一下...

答案解析:

解这类题的关键,就是把那些重复的情况,排斥出去.

观察题目,既然是要找不能被整除的数,那么我们就把能整除的数给排出去.

很容易就能推出,两种情况分别为 \(\lfloor \frac{1000}{5} \rfloor\)\(\lfloor \frac{1000}{6} \rfloor\).那么只有这些情况了吗?

稍微思考一下,可以发现会有同时被5和6整除的数,这个时候,我们就要统计这部分,即 \(\lfloor \frac{1000}{30} \rfloor\) ,30为5和6的最小公倍数.

那么最后,我们能得出结果为 1000 - 200 - 166 + 33,或者抽象一点,结果为 \(|A| - |B| - |C| + |B \cap C|,A = \{1,2,...,1000\},B = \{5,10,...,1000\},C = \{6,12,...,969\}\).

那么数学题有了,就该来道编程题了!

牛客网-小y的质数:

题目描述:

定义k生互质数为满足y + ky - k互质的数.

现在给出区间 \([L,R]\)\(k\),请求出在区间内有多少对k生互质数.

注意,一对k生互质数中的两个数x和y,都要满足 \(L \le x,y \le R\).

那么这道题,我们如何用容斥的思想解决呢?

观察题目,既然是要互质,那么我们也可以定义为 \(gcd(y - k,y + k) = 1\) 的形式,
如果将y - k定义为x,那么 也可以将其理解为 \(gcd(x,x + 2k) = 1\).

在数论中,我们有了解过更相减损术,这种算法,因此原式进一步转化为 \(gcd(x,2k) = 1\)

那么现在目的很明显了,查找在区间 \([L,R]\) 中与2k互质的数的数量.

如果要使得两个数互质,是不是要让其中一个数的质因子与另一个数的完全避开?

举个例子,假设2k = 20。那么他的质因子就有2和5,那么我们要做的就是要把所有包含2或5这两个质因子的数通通筛掉?

当然,在筛的过程中,固然会有重复的情况,那么,我们如何确定,每次重复的情况是要加还是减呢?

如果将上文的问题中不能被整除的数增加一个话,那么结果就是 \(|A| - |B| - |C| - |D| + |B \cap C| + |B \cap D| + |C \cap D| - |B \cap C \cap D|\).

如果大胆猜测一下,我们是不是能的出奇负偶正的道理?

那么有了如何确定正负的方法,那么代码就可以展示了.

注意:在枚举倍数时不同的质因子相乘会有不同的结果,要全部列举出来.

#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll l,r,k,p[200005],num,s;
ll sum(ll x){//寻找小于等于x的不包含p中的质因子的数的数量
	if(x < 0) return 0;//防止l - 1越界
	ll res = 0;
	for(ll i = 0;i < (1 << num);i++){
		ll f = 1,tmp = 1;
		for(ll j = 0;j < num;j++){
			if(1 << j & i){
				tmp *= p[j];
				f *= -1;
			}
		}
		res = res + (x / tmp) * f;
	}
	return res;
}
int main(){
	cin >> l >> r >> k;
	k *= 2, r -= k;
	s = sqrt(k);
	if((l + k) > r){cout<<0;return 0;} // 如果区间长度太小,则直接输出0.
	for(int i = 2;i <= s;i++){//质因数分解
		if(k % i == 0)
			p[num++] = i;
		while(k % i == 0) k /= i;
	}
	if(k != 1) p[num++] = k;
	cout<<sum(r) - sum(l - 1);
	return 0;
}

那么上面的问题都是针对单个集合,那么多个集合呢?

多个集合的容斥原理应用-错排列:

先看定义:

对于n个元素1~n进行排列,要求i号元素不能在i号位置.请求出可能的方案数,并对1e9 + 7取模.

既然不能在这个位置,那么我们就把在这个位置的情况筛出来.

如果只有一个i号元素在i号位置,那么有多少种可能呢?i又有多少种选法呢?

解析:因为i号元素被固定了,那么就剩下n - 1个元素,根据全排列可求出有 \((n - 1)!\) 种可能.

i的选法就是从n个元素中选出i个元素的可能数,可以得出为 \(C_n^1 = n\).

那么一个元素的情况筛了出来,就要把重复的情况加回去.
以两个元素为例,那么根据上文,是不是能推出排列数为 \((n - 2)!\) ,两个元素的选法为 \(C_n^2\).

由于上面的题目得到了"奇负偶正"的结论,所以这时题目的答案就得出来了.结果为:

\[\sum_{i = 0}^n (-1)^i (n - i)! C_n^i \]

由于直接硬求组合数会炸掉,所以我们可以通过杨辉三角递推求解.

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const long long mod = 1e9 + 7;
ll n,jc[1005],c[1005][1005],cnt;
int main(){
    cin >> n;
    jc[0] = 1;
    for(int i = 1;i <= n;i++) {jc[i] = (jc[i - 1] * i) % mod;}//阶乘
    for(int i = 0;i <= n;i++) {c[i][0] = 1;c[i][i] = 1;} //杨辉三角每排第一和最后一个数都是1,预处理
    for(int i = 1;i <= n;i++){//杨辉三角
        for(int j = 1;j < i;j++)
            c[i][j] = (c[i-1][j-1] + c[i-1][j]) % mod;
    }
    for(int i = 0;i <= n;i++) {cnt = (cnt + ((ll)(pow(-1,i)) * c[n][i] * jc[n - i]) % mod) % mod;}
    cout<<cnt;
    return 0;
}

--------------------------------------------分割线------------------------------------------------------

参考文献:

百度百科-容斥原理

数学符号列表-集合

谢谢!有问题还请多多指出.

posted @ 2024-11-02 12:39  Cai_hy  阅读(60)  评论(0)    收藏  举报