大家好,欢迎来到IT知识分享网。
容斥定理
简单版本:
对于上述图片,求 ∣ A ∪ B ∪ C ∣ |A\cup B \cup C| ∣A∪B∪C∣
结果为 ∣ 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| ∣A∪B∪C∣=∣A∣+∣B∣+∣C∣−∣A∩B∣−∣B∩C∣−∣C∩A∣+∣A∩B∩C∣
一般情况
公式: ∣ ⋃ 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)m−1∑ai<ai+1∣⋂i=1mSai∣
大家应该是看不懂吧,反正我是看不懂
我理解的通俗的意思就是:
n
个集合的并集等于n
个集合选择一个的情况中所有情况的交-n
个集合中选择两个所有情况中两两的交+n
个集合中选择三个中所有情况三个的交-选择四种的交+选择五种的交-…
大概就是这个意思。
- 选择的个数为偶数次,前面符号为负
- 选择的个数为奇数次,前面符号为正
例题1 能被整除的数
题目链接:https://www.acwing.com/problem/content/892/
给定一个整数
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中的至少一个数整除的整数有多少个。
思路:
在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,xi≥0,总方案数为 C n + m − 1 n − 1 C_{n+m-1}^{n-1} Cn+m−1n−1种
- 有限制的情况
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+m−1n−1
- 题目的补集: ∣ s 1 ⋃ s 2 ⋃ s 3 … … ⋃ s n ∣ |s_1\bigcup s_2\bigcup s_3……\bigcup s_n| ∣s1⋃s2⋃s3……⋃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+n−1n−1−∣s1⋃s2⋃s3……⋃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+n−1−(A1+1)n−1
s 1 ∩ s 2 s_1\cap s_2 s1∩s2同理
答案为 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+n−1−(A1+1)−(A2+1)n−1
最后枚举所有的情况数,使用二进制方法进行枚举,枚举 [ 0 , 2 n − 1 ] [0,2^n-1] [0,2n−1],统计二进制位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) (H−h+1)×(W−w+1) 个,每个矩形框对应的分数为 h × w h \times w h×w
容斥时,共有四个变量,对应4个二进制位, 对应上下左右四个方向是否收缩1个单位,收缩的总数为奇数时,符号为负,为偶数时,符号为正。
注意容斥变量s是从0开始,0刚好对应总的情况数,所以不用额外计算总的情况数
最后再除以总的情况数 C h ∗ w k C_{h * w} ^ k Ch∗wk 即可
#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