算法学习笔记(41)——容斥原理
容斥原理
设 为有限集合, 表示集合 的大小,则:
可以用文氏图来宏观地描述容斥原理,如下图所示:
组合数的性质:
从n个数里面挑0个数的方案数,加上挑1个数的方案数...,一直加到挑n个数的方案数,等价于从n个数中选任意多个数,每个数都是挑或不挑的所有方案数。
所以 ,共有 项,时间复杂度为
- 每个集合实际上并不需要知道具体元素是什么,只要知道这个集合的大小,大小为
- 交集的大小如何确定?因为 均为质数,这些质数的乘积就是他们的最小公倍数, 除这个最小公倍数就是交集的大小,故
- 如何用代码表示每个集合的状态?这里使用的二进制,位运算枚举所有方案,从 枚举到 ,用每一个数代表一种选法(二进制形式)。以 为例,所以需要 个二进制位来表示每一个集合选中与不选的状态, ,这里表示选中集合 ,故这个集合中元素的个数为 , 因为集合个数是 个,根据公式,前面的系数为 。所以到当前这个状态时,应该是 。这样就可以表示的范围从 到 的每一个状态
用二进制表示状态的小技巧非常常用,后面的状态压缩DP也用到了这个技巧,因此一定要掌握
C++程序1s内可以计算 ~ 次,本题中 ,不会超时。
~ 中 的倍数的个数是
时间复杂度:
#include <iostream>
using namespace std;
typedef long long LL;
const int N = 20;
int n, m;
int primes[N];
int main()
{
cin >> n >> m;
for (int i = 0; i < m; i ++ ) cin >> primes[i];
int res = 0; // 用于存储结果(满足条件的整数个数)
// 枚举从1到2^n - 1(111...1)的数,代表所有的选择方案
for (int i = 1; i < 1 << m; i ++ ) {
int t = 1; // 存储当前选择方案里的质数的乘积
int s = 0; // 存储选中的方案的数量,根据奇偶决定正负号
// 枚举当前方案的每一位,1表示当前方案选择了当前位的数字,0表示没有选择
for (int j = 0; j < m; j ++ ) {
// 如果当前为数字是1,代表选择了该数字
if (i >> j & 1) {
// 乘积大于n,则 n/p[0]*...*p[j] = 0,则n被分母的几个质数整除的交集大小是0
if ((LL)t * primes[j] > n) {
t = -1; // 标记当前方案不符合,跳出循环,判断下一方案
break;
}
s ++; // 统计当前方案内的数字个数
t *= primes[j];
}
}
if (t == -1) continue;
// 二进制末尾是1则为奇数,等价于s % 2 != 0
if (s & 1) res += n / t;
else res -= n / t;
}
cout << res << endl;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】