本文已参与「新人创作礼」活动,一起开启掘金创作之路。
容斥定理
简单版本:
对于上述图片,求
结果为
一般情况
公式:
大家应该是看不懂吧,反正我是看不懂
我理解的通俗的意思就是:
n
个集合的并集等于n
个集合选择一个的情况中所有情况的交-n
个集合中选择两个所有情况中两两的交+n
个集合中选择三个中所有情况三个的交-选择四种的交+选择五种的交-......
稍微用公式表示一下:
大概就是这个意思。
- 选择的个数为偶数次,前面符号为负
- 选择的个数为奇数次,前面符号为正
参考链接:
oi-wiki.org/math/combin…
例题1 能被整除的数
题目链接:www.acwing.com/problem/con…
给定一个整数
n
和m
个不同的质数 p 1 , p 2 , … , p m p_1,p_2,…,p_m p1,p2,…,pm。
请你求出 1∼n 中能被 p 1 , p 2 , … , p m p_1,p_2,…,p_m p1,p2,…,pm中的至少一个数整除的整数有多少个。
思路:
先简单的举个例子:
质数有2,3,5,7,11五个
能被2整除的有2,4,6,8 …
能被3整除的有3,6,9,12…
… 问的是被至少一个整除就行,那么上述例子中6是重复的 也就是我们可以把能被一个质数整除的个数当作一个集合,这么多质数组成的集合有重合的,我们要求的是这么多集合的并集,满足容斥定理
在1-n
中能被x
整除的个数:
在1-n
中能被x,y
整除的个数:
在1-n
中能被x,y,z
整除的个数:
......
然后就可以根据公式求结果,有m
个质数,共有m
个集合,每次会选中若干个集合,代表几个之间的交集(参照上面容斥定理公式)
几个集合之间的交集:就是一个数能同时被这选中的几个质数整除
选中集合个数为偶数,前面符号为负 选中集合个数为奇数,前面符号为正
枚举所有的集合:
我们采用二进制的方法,枚举,统计其中
1
的个数即可
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 20;
int p[N];
int n,m;
int main()
{
cin>>n>>m;
for(int i=0;i<m;i++) cin>>p[i];
ll res = 0;
for(int i=1;i< 1<<m ;i++)
{
int cnt = 0;
ll t = 1;
for(int j=0;j<m;j++)
{
if( i>>j & 1)
{
cnt ++ ;//统计选中的个数
if(t * p[j] > n)//不满足条件,因为大于n了
{
t = -1;
break;
}
t *= p[j];
}
}
if(t!=-1)
{
if(cnt%2) res += n/t;
else res -= n/t;
}
}
cout << res << endl;
return 0;
}
例题2
题目链接:www.acwing.com/problem/con…
Devu 有
N
个盒子,第i
个盒子中有 A i A_i Ai 枝花。同一个盒子内的花颜色相同,不同盒子内的花颜色不同。Devu 要从这些盒子中选出m
枝花组成一束,求共有多少种方案。若两束花每种颜色的花的数量都相同,则认为这两束花是相同的方案。结果需对 1 0 9 + 7 10^9+7 109+7 取模之后方可输出
思路
- 理想情况
首先去掉限制考虑理想情况,即每个盒子的花的个数有无限个,设第个盒子取出朵花
则,总方案数为种
可以参考链接查看如何计算 : oi-wiki.org/math/combin…
- 有限制的情况
限制条件下:
同时满足限制条件才可,我们考虑从补集去求(总情况数减去相反的情况)
补集情况下:
第i
个限制()的情况满足个数我们当作一个集合
-
总数:
-
题目的补集:
-
结果为:
考虑求满足的情况数
即第一个必须取至少支花,那么接下来就化为从n
组里面选花,
的情况,可以直接取出支花放在第一组,那么总数就变成支花,就化为理想情况(见上文)下的问题了,总数为
同理
答案为
最后枚举所有的情况数,使用二进制方法进行枚举,枚举,统计二进制位1
的个数
奇数个1
符号为负
偶数个1
符号位正
#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 20,mod = 1e9+7;
ll a[N];
ll d = 1;
ll fast(ll a,ll b)
{
ll res = 1;
while(b)
{
if(b&1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
ll C(ll x,ll y)
{
if(x < y) return 0;
ll u = 1;
for(ll i = x;i>x-y;i--) u = i % mod * u % mod;
return u * d % mod;
}
int main()
{
ll n,m;
cin >> n >> m;
for ( int i=0;i<n;i++) cin>>a[i];
for(ll i=1;i<=n-1;i++) d = d * i % mod;
d = fast(d,mod-2);
ll res = 0 ;
for(int i=0; i< 1<<n;i++)
{
//组合数的下角标 上角标
ll down = n + m -1,up = n-1;
int sign = 1;//标记的符号位
for(int j=0;j<n;j++)
{
if(i>>j&1)
{
down -= a[j]+1;//下角标减去对应的个数
sign *= -1;
}
}
res = (res + C(down,up) * sign)%mod; //计算组合数,统计答案
}
cout<<(res + mod) % mod<<endl;
return 0;
}
往期优质文章推荐
领取大量学习资源
涵盖python基础 + 进阶 + 精通所有课程
还有许多C++等众多资料等待你的开发
资源始终不是最重要的一环,关键是如何找资源才是最重要的,之后我会整理出怎样找资源,从哪里找资源,如何高效找资源的文章发于公众号上,感谢大家支持!