「学习笔记」容斥原理

引入#

A1:学语文的人, A2:学数学的人,A3:学英语的人,A4:学 OI 的人

A1A2:同时学语数的人

A1A2:学语文或数学的人

|A1A2|=|A1|+|A2||A1A2|

|A1A2A3|=|A1|+|A2|+|A3||A1A2||A1A3||A2A3|+|A1A2A3|

|A1A2A3A4|=|A1|+|A2|+|A3|+|A4||A1A2||A1A3||A2A3||A1A4||A2A4||A3A4|+|A1A2A3|+|A1A2A4|+|A2A3A4||A1A2A3A4|


我总结了一句话

容斥原理,就是总共的减去重复的加上缺失的。

容斥原理的一般式

b{A1,A2,,An}(1)|B|+1|aiBAi|

问题#

n 对夫妻坐成一圈,每对夫妻不能坐在一起,问方案数。

总的方案数为 2n!2n=(2n1)!
我们将夫妻绑定在一起,这样,这对夫妻可以看作是一个人。
来计算不合法方案数:
一对夫妻坐在一起时,方案数为 (2n2)!,在 n 对夫妻中选一堆绑定,并且夫妻之间也有坐的顺序,因此一对夫妻坐在一起的非法方案数为 2C(n,1)(2n2)!
但是,在这一对夫妻坐在一起的方案数中,包含了两对夫妻坐在一起、三对夫妻坐在一起的情况,因此,有重复计算的,两对夫妻坐在一起被计算了两次,三对夫妻坐在一起被计算了三次。
我们要减去两对夫妻坐在一起的情况的方案数,即 22C(n,2)(2n3)!,在这两对夫妻坐在一起的情况里,同样,也包含了三对夫妻坐在一起的情况,而这一减,三对夫妻坐在一起的方案数直接变为 0 了,因此我们需要再把他们加上。
由此,就能够看出有容斥的规律了,我们往后推,减去四对夫妻坐在一起的方案数,加上 5 对夫妻坐在一起的方案数。。。
因此,总的非法方案数就是 2C(n,1)(2n2)!C(n,2)(2n3)!22+C(n,3)(2n4)!23
总的方案数减去非法方案数就是 (2n1)!2C(n,1)(2n2)!+C(n,2)(2n3)!22C(n,3)(2n4)!23

i=0nC(n,i)(2ni1)!2i(1)i


询问 1n 中有多少个数可以表示为 xyy>1 的形式。(n1018)

由于 long long 的范围可知,可行的 y 小于等于 64
在这 n 个数中,能表示成 x2 的数有 n 个,能表示成 x3 的数有 n3 个,能表示成 xy 的数有 ny 个。
但是,我们的答案就是 i=2yni 吗?
很明显,不是。
看一看这种情况,y6=(y3)2=(y2)3,很明显,直接求和会有重复,因此,我们要减去重复的部分

cin >> n;
for (int a = 2; a <= 64; ++ a) {
    num[a] = 0;
// num[a] 代表 x^a 这种形式的数被算了几次
}
for (int a = 2; a <= 64; ++ a) { // 算 x^a 这种形式的数有多少个
    ll v = floor(pow(n, (long double)1.0 / a)) - 1;
//  ll v = (ll)floor(exp(log(n) / a)) - 1;
    int d = 1 - num[a];
    ans += v * d;
    for (int b = a; b <= 64; b += a) {
        num[b] += d;
    }
}

代码解释#

num[a] 代表 xa 这种形式的数被算了几次,很明显,我们希望最后每个 num 都为 1,因此,我们需要加上少的或者减去多的。

int d = 1 - num[a];
ans += v * d;`

最后,我们再更新 num

for (int b = a; b <= 64; b += a) {
	num[b] += d;
}

这段代码可以这么理解
假设我们计算 x2,我们会算到 224282,我们可以将它们转化一下,即 222426,因此,只要是指数是二的倍数的形式的数,我们都能算到。

真题#

[春季测试 2023] 幂次
这道题对于 pow 有精度要求,要用 long double,或者用 exp,否则最后一个点过不去。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

ll n, k, ans;
ll num[70];

int main() {
	ios::sync_with_stdio(false);
	cin.tie(0),	cout.tie(0);
	cin >> n >> k;
	for (int a = k; a <= 64; ++ a) {
		num[a] = 0;
	}
	for (int a = k; a <= 64; ++ a) {
		ll v = floor(pow(n, (long double)1.0 / a)) - 1;
//		ll v = (ll)floor(exp(log(n) / a)) - 1;
		ll d = 1 - num[a];
		ans += v * d;
		for (int b = a; b <= 64; b += a) {
			num[b] += d;
		}
	}
	cout << ans + 1 << '\n';
	return 0;
}

作者:yifan0305

出处:https://www.cnblogs.com/yifan0305/p/17454094.html

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

转载时还请标明出处哟!

posted @   yi_fan0305  阅读(152)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
more_horiz
keyboard_arrow_up light_mode palette
选择主题
menu
点击右上角即可分享
微信分享提示