【离散数学】容斥原理

【离散数学】容斥原理容斥定理详解及经典习题 容斥原理离散数学

大家好,欢迎来到IT知识分享网。

容斥定理

简单版本:

在这里插入图片描述
对于上述图片,求 ∣ A ∪ B ∪ C ∣ |A\cup B \cup C| ABC
结果为 ∣ A ∪ B ∪ C ∣ = ∣ A ∣ + ∣ B ∣ + ∣ C ∣ − ∣ A ∩ B ∣ − ∣ B ∩ C ∣ − ∣ C ∩ A ∣ + ∣ A ∩ B ∩ C ∣ |A\cup B\cup C|=|A|+|B|+|C|-|A\cap B|-|B\cap C|-|C\cap A|+|A\cap B\cap C| ABC=A+B+CABBCCA+ABC

一般情况

公式: ∣ ⋃ i = 1 n S i ∣ = ∑ m = 1 n ( − 1 ) m − 1 ∑ a i < a i + 1 ∣ ⋂ i = 1 m S a i ∣ \left|\bigcup_{i=1}^{n}S_i\right|=\sum_{m=1}^n(-1)^{m-1}\sum_{a_i<a_{i+1} }\left|\bigcap_{i=1}^mS_{a_i}\right| i=1nSi=m=1n(1)m1ai<ai+1i=1mSai

大家应该是看不懂吧,反正我是看不懂

我理解的通俗的意思就是:

n个集合的并集等于n个集合选择一个的情况中所有情况的交-n个集合中选择两个所有情况中两两的交+n个集合中选择三个中所有情况三个的交-选择四种的交+选择五种的交-…

大概就是这个意思。

  • 选择的个数为偶数次,前面符号为负
  • 选择的个数为奇数次,前面符号为正

例题1 能被整除的数

题目链接:https://www.acwing.com/problem/content/892/

给定一个整数 nm 个不同的质数 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中的至少一个数整除的整数有多少个。


思路:

1-n中能被x整除的个数: ⌊ n x ⌋ \lfloor \frac{n}{x}\rfloor xn
1-n中能被x,y整除的个数: ⌊ n x y ⌋ \lfloor \frac{n}{xy}\rfloor xyn
1-n中能被x,y,z整除的个数: ⌊ n x y z ⌋ \lfloor \frac{n}{xyz}\rfloor xyzn

然后就可以根据公式求结果,有m个质数,共有m个集合,每次会选中若干个集合,代表几个之间的交集(参照上面容斥定理公式)



几个集合之间的交集:就是一个数能同时被这选中的几个质数整除

选中集合个数为偶数,前面符号为
选中集合个数为奇数,前面符号为

#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

题目链接:https://www.acwing.com/problem/content/216/

Devu 有 N 个盒子,第 i 个盒子中有 A i A_i Ai 枝花。同一个盒子内的花颜色相同,不同盒子内的花颜色不同。Devu 要从这些盒子中选出 m 枝花组成一束,求共有多少种方案。若两束花每种颜色的花的数量都相同,则认为这两束花是相同的方案。结果需对 1 0 9 + 7 10^9+7 109+7 取模之后方可输出


思路

  • 理想情况

首先去掉限制考虑理想情况,即每个盒子的花的个数有无限个,设第 i i i个盒子取出 x i x_i xi朵花

x 1 + x 2 + x 3 + . . . + x n = m , x i ≥ 0 x_1+x_2+x_3+…+x_n= m,x_i \geq 0 x1+x2+x3++xn=m,xi0,总方案数为 C n + m − 1 n − 1 C_{n+m-1}^{n-1} Cn+m1n1


  • 有限制的情况

x 1 , x 2 , . . . , x n x_1,x_2,…,x_n x1,x2,,xn同时满足限制条件才可,我们考虑从补集去求(总情况数减去相反的情况)

补集情况下:

x 1 + x 2 + x 3 + . . . + x n = m , x 1 > A 1 , x 2 > A 2 , x 3 > A 3 … … x n > A n x_1+x_2+x_3+…+x_n= m,x_1>A_1,x_2>A_2,x_3>A_3……x_n>A_n x1+x2+x3++xn=m,x1>A1,x2>A2,x3>A3……xn>An
i个限制( x i > A i x_i>A_i xi>Ai)的情况满足个数我们当作一个集合 s i s_i si

  • 总数: C n + m − 1 n − 1 C_{n+m-1}^{n-1} Cn+m1n1
  • 题目的补集: ∣ s 1 ⋃ s 2 ⋃ s 3 … … ⋃ s n ∣ |s_1\bigcup s_2\bigcup s_3……\bigcup s_n| s1s2s3……sn
  • 结果为: C m + n − 1 n − 1 − ∣ s 1 ⋃ s 2 ⋃ s 3 … … ⋃ s n ∣ C_{m+n-1}^{n-1}-|s_1\bigcup s_2\bigcup s_3……\bigcup s_n| Cm+n1n1s1s2s3……sn

考虑求满足 s 1 s_1 s1的情况数
即第一个必须取至少 A i + 1 A_i+1 Ai+1支花,那么接下来就化为从n组里面选花, x 1 > = A 1 + 1 , x 2 > = 0 , x 3 > = 0 , . . . , x n > = 0 x_1>=A_1+1,x_2>=0,x_3>=0,…,x_n>=0 x1>=A1+1,x2>=0,x3>=0,,xn>=0的情况,可以直接取出 A i + 1 A_i+1 Ai+1支花放在第一组,那么总数就变成 m − ( A 1 + 1 ) m-(A_1+1) m(A1+1)支花,就化为理想情况(见上文)下的问题了,总数为 C m + n − 1 − ( A 1 + 1 ) n − 1 C_{m+n-1-(A_1+1)}^{n-1} Cm+n1(A1+1)n1

s 1 ∩ s 2 s_1\cap s_2 s1s2同理
答案为 C m + n − 1 − ( A 1 + 1 ) − ( A 2 + 1 ) n − 1 C_{m+n-1-(A_1+1)-(A_2+1)}^{n-1} Cm+n1(A1+1)(A2+1)n1

最后枚举所有的情况数,使用二进制方法进行枚举,枚举 [ 0 , 2 n − 1 ] [0,2^n-1] [0,2n1],统计二进制位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; } 

例题3 ABC 297 F

在这里插入图片描述

枚举矩形的长h和宽w,长h宽w的矩形的情况数总共有 ( H − h + 1 ) × ( W − w + 1 ) (H – h + 1) \times (W – w + 1) (Hh+1)×(Ww+1) 个,每个矩形框对应的分数为 h × w h \times w h×w

容斥时,共有四个变量,对应4个二进制位, 对应上下左右四个方向是否收缩1个单位,收缩的总数为奇数时,符号为负,为偶数时,符号为正。

注意容斥变量s是从0开始,0刚好对应总的情况数,所以不用额外计算总的情况数

最后再除以总的情况数 C h ∗ w k C_{h * w} ^ k Chwk 即可

#include<bits/stdc++.h> using namespace std; using ll = long long; const int mod = ; const int N = 1e6; ll ksm(ll a, ll b) { 
        ll res = 1; a %= mod; while(b) { 
        if(b & 1) res = res * a % mod; b >>= 1; a = a * a % mod; } return res; } int main() { 
        ios::sync_with_stdio(false); cin.tie(0); int h, w, k; cin >> h >> w >> k; vector<ll> fac(N + 1), inv(N + 1); fac[0] = 1; for(int i = 1; i <= N; i++) { 
        fac[i] = fac[i - 1] * i % mod; } inv[N] = ksm(fac[N], mod - 2); inv[0] = 1; for(int i = N - 1; i >= 1; i--) { 
        inv[i] = inv[i + 1] * (i + 1) % mod; } auto C = [&](ll n, ll m) -> ll { 
        if(n < m || m < 0) return 0; return fac[n] * inv[m] % mod * inv[n - m] % mod; }; ll ans = 0; for(int i = 1; i <= h; i++) { 
        for(int j = 1; j <= w; j++) { 
        ll res = 0; for(int s = 0; s < 16; s++) { 
        int u = __builtin_popcount(s & 3); int v = __builtin_popcount(s & 12); res += ((u + v) & 1 ? -1 : 1) * C(max(0, i - u) * max(0, j - v), k) % mod; res %= mod; } ans += res * (h - i + 1) % mod * (w - j + 1) % mod * i % mod * j % mod; ans %= mod; } } ans *= ksm(C(h * w, k), mod - 2); ans %= mod; cout << (ans + mod) % mod << "\n"; return 0; } 

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/126591.html

(0)
上一篇 2025-09-17 17:20
下一篇 2025-09-17 17:26

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信