算法 {容斥原理,二进制子集的容斥}
@MARK_1
容斥原理
定义
給定若干去重集合Si
, 則
S
1
∪
S
2
∪
.
.
.
∪
S
n
=
+
i
S
i
−
+
i
,
j
(
S
i
∩
S
j
)
+
+
i
,
j
,
k
(
S
i
∩
S
j
∩
S
k
)
−
.
.
.
S1 \cup S2 \cup ... \cup Sn = +_i Si - +_{i,j}(Si \cap Sj) + +_{i,j,k}(Si\cap Sj \cap Sk) - ...
S1∪S2∪...∪Sn=+iSi−+i,j(Si∩Sj)++i,j,k(Si∩Sj∩Sk)−..., 其中的+ -
指的是集合加法,集合減法;
即
2
n
2^n
2n遍歷全組合, 如果是奇數個項 比如S1S2S3
則執行加法, 否則比如S1S2
則執行減法;
.
比如S1={a,b,e} S2={b,c,e} S3={c,d,e}
, 則有S1 U S2 U S3 == (S1+S2+S3) - (S1S2 + S1S3 + S2S3) + (S1S2S3) == {abe,bce,cde} - {be,e,ce} + {e} == {a,b,c,d}
;
算法
代碼模板
求`ANS = S0 U S1 U S2 U ... U S[n-1]`;
ANS = {};
for( int st = 1; st < (1<<n); ++st){
vector<int> Ind = `st裡的所有1的下標`;
S := `S[Ind.front()] 交集 ... 交集 S[Ind.back()]`;
if( Ind.size() & 1){
ANS 集合加法 S;
}
else{
ANS 集合減法 S;
}
}
二进制子集的容斥原理
@LINK: @MARK_0
;
性质
容斥的核心是: 去掉重复的元素;
@DELI;
#去除多餘的集合#
令T:= S1 U S2 U ... U Sn
, 對於Si
令
S
R
=
U
j
≠
i
S
j
SR = U_{j \neq i} Sj
SR=Uj=iSj, 如果
S
i
⊂
S
R
Si \subset SR
Si⊂SR 則SR == T
; 也就是 你可以把Si
給去掉;
.
比如S1={a}, S2={b}, S3={c}, S4={a,b}, S5={a,c}, S6={b}, S7={a,b,c}
, 那麼S1 U ... U S7 == S1 U S2 U S3
, 即可以把S4,S5,S6,S7
給去掉, 即原來容斥原理是2^7
量級 現在優化為2^3
;
例題: @LINK: https://editor.csdn.net/md/?not_checkout=1&articleId=130248080
;
@DELI;
来一个经典例题来熟悉容斥原理;
给定
N
N
N个数
[
a
1
,
.
.
.
,
a
n
]
[ a_1, ..., a_n]
[a1,...,an], 令布尔函数f(x) = (a1 | x) || (a2 | x) ... || (an | x);
(即只要可以被至少一个ai
所整除, 则x
返回True);
.
求
x
∈
[
1
,
M
]
x \in [1, M]
x∈[1,M]范围内 f(x) = True
的个数;
我们知道 对于一个数
x
x
x, [1, N]
范围内 会有N / x
个数可以整除x
(即x, 2x, 3x, ...
);
.
所以, 可以定义S_x
表示所有可以整除x
的元素 所组成的集合, 用C(x)
表示
∣
S
x
∣
|S_x|
∣Sx∣;
.
那么, 答案即为
∣
S
a
1
∪
S
a
2
∪
.
.
.
∪
S
a
n
∣
| S_{a_1} \cup S_{a_2} \cup ... \cup S_{a_n}|
∣Sa1∪Sa2∪...∪San∣; 根据上面的转换公式, 你需要求
∣
S
i
∩
S
j
∣
|S_i \cap S_j|
∣Si∩Sj∣ (这里只是做一般性分析, 当然
∣
.
.
.
∣
|...|
∣...∣中 可能包含[1/2/3/…/N]个
S
i
S_i
Si, 这里以2个为例子);
.
.
容斥原理, 最最重要的, 就是求这个值, 即若干个Si
的并集 的大小; 他要怎么求, 你需要具体问题具体分析, 通常都是用C(x)
这个函数 来求他;
.
.
在这里,
S
i
∩
S
j
S_i \cap S_j
Si∩Sj表示: 既可以整除i
又可以整除j
的 元素个数; 你必须要搞懂这个逻辑式, 他的本质是 可以整除z = LCM(i,j)
(最大公倍数), 因此
∣
S
i
∩
S
j
∣
=
C
(
z
)
|S_i \cap S_j| = C(z)
∣Si∩Sj∣=C(z);
.
.
因此, 答案 就是 一堆C(x)
的相加减;
–
容斥原理的前提条件:
容斥原理 一定求的是
N
N
N个集合的并集的大小, 也就是 遇到容斥原理 你必须要找到一些集合 使得这些结合的并集, 就是答案集合
.
反过来说, 假如答案集合为S
, 你必须将他可重复性的划分 (两个子集可以相交) 为若干子集合, 使得这些集合的并集 等于S
;
–
容斥原理的计算方式:
容斥原理的结果, 就是一堆
∣
S
i
∩
S
j
∩
S
k
∣
|S_i \cap S_j \cap S_k|
∣Si∩Sj∩Sk∣之和 (他是有符号的, 取决于i,j,k,...
的个数, 如果是奇数 则为1, 否则为-1
);
.
∣
S
i
∩
S
j
∩
S
k
∣
|S_i \cap S_j \cap S_k|
∣Si∩Sj∩Sk∣ 这个值, 是通过一个函数C(x)
来计算的;
–
容斥原理的常用算法
.
1 1 << N
时间
ans = 0;
for( st = 1; st < (1 << N); ++st){
bit_count = `st中1的个数`;
z = `st中所有1对应的A[]值的最小公倍数`;
if( bit_count & 1) ans += C(z); // `C(z) = M / z`;
else ans -= C(z);
}
.
2 M
次循环 @MARK_0
.
.
不要认为, 容斥原理就一定是1 << N
的时间; 你一定要结合公式, 因为我们知道 容斥原理 即答案 就等于一堆C(x)
之和, 在本题中 C(x) = M / z
, 自然对于C( > M)
他的值一定是0
, 所以可以跳过不管的;
.
.
假如题目说了 这N个数 是互不相同的质数, 那么, 对于这
2
n
−
1
2^n - 1
2n−1个项
∣
S
i
∩
S
j
∩
S
k
∣
=
C
(
z
)
|S_i \cap S_j \cap S_k| = C( z)
∣Si∩Sj∩Sk∣=C(z) (
z
=
L
C
M
(
i
,
j
,
k
)
z = LCM(i,j,k)
z=LCM(i,j,k)) 与 z
形成双射; 即不同的项 对应的z
(注意不是C(z)
而是z
) 是不同的;
.
.
因为这里z
的定义是: z = i * j * k
, 因为i,j,k
都是不同的质数 所以组成z
一定不同;
.
.
假如把互不相同这个条件去掉, 仅仅只是质数, 那这个性质就不存在了; 比如有两个相同质数p, p
, 那么有两个项 都是
∣
S
p
∣
|S_p|
∣Sp∣ 自然他们对应的z
是相同的;
.
.
假如把质数这个条件去掉, 仅仅只是互不相同, 那这个性质也不存在了; 比如2, 3, 6
, 那么
S
6
S_6
S6 与
S
2
∩
S
3
S_2 \cap S_3
S2∩S3 都对应z = 6
;
.
.
这里有了这个性质, 即这
2
n
−
1
2^n - 1
2n−1个项 对应的z
值均不同; (假如题目中
n
=
1000
n = 1000
n=1000 肯定是不能枚举所有的项 来求容斥原理了; 而且, z
值可能就无法求出 因为这
n
n
n个数的乘积 溢出long long
); 此时, 一定要结合C(z)
这个函数, 因为我们的本质 就是求这个函数; 根据该函数在这道题的定义 C(z) = M / z
, 那么显然, 对于C( > M) = 0
, 因此 对于所有对应z > M
的项 我们根本就无需枚举这些项;
.
.
因此, 只考虑z = [1, ..., M]
范围内的项, 令MM= min( M, 这n个数的乘积)
, 那么 对这些项对应的<= MM
的z
值 一定是在[1, ..., MM]
范围内的 (但不是满射, 比如z = 4
没有项的z = 4
); 因此, 对于一个z
值, 设置一个Flag(z)
函数 [如果z
质因数分解的最高次> 1
, 则为0],[如果z
的质因数个数为奇数, 则为1],[如果z
的质因数个数为偶数, 则为-1
]; (这个Flag(z)
就是莫比乌斯函数)
.
.
此时原式变为:
∑
i
=
1
M
C
(
i
)
∗
F
l
a
g
(
i
)
\displaystyle \sum_{i = 1}^{M} C(i) * Flag(i)
i=1∑MC(i)∗Flag(i), 即 我们将
O
(
2
n
)
→
O
(
M
)
O(2^n) \to O(M)
O(2n)→O(M); 因为本题里
C
(
i
)
C(i)
C(i)是下取整函数, 那么
C
(
i
)
C(i)
C(i)的取值 只有
2
M
2\sqrt{M}
2M个, 因此 可以再使用下取整的前缀和优化 对Flag(i)
进行前缀和处理, 然后i
直接从l
跳到r + 1
(即i= [l, l+1, ..., r]
对应的C(i)
都是相同的); 就从
O
(
M
)
→
O
(
M
)
O(M) \to O(\sqrt{M})
O(M)→O(M);
例题
CSDN--130248080
: 基于@MARK_0
, 即不是1<<N
的枚举, 而是O(M)
的枚举, 再用下取整优化到O(sqrt(M))
–
https://editor.csdn.net/md/?articleId=130220207
.
变形的隔板法; C[i] <= M[i]
是合法方案, 他的非法方案为C[i] > M[i]
, 则使用容斥原理求非法方案, 则合法方案 = 朴素隔板法 - 容斥原理
;
二进制子集的容斥原理
@MARK_0
;
定义
&N:
二进制长度; &Subs(st): st的子集
(比如&Subs(101)={101,100,001,000}
); &Raw[st]:
每个二进制对应的价值; 已知&Sum[st]: Raw[st子集]之和
; (比如&Sum[101] = Raw[101,100,001,000]之和
;
求 某个特定的&Raw[x]
;
答案: &Raw[st] = (flag(x) * Sum[x])之和
(x为&Subs(st)
, flag(x) = {x,st}的二进制中1的个数的奇偶性相同? : 1 : -1)
);
.
比如Raw[101] = Sum[101] - Sum[100] - Sum[001] + Sum[000]
; 比如Raw[1101] = Sum[1101] - Sum[1100] - Sum[1001] - Sum[0101] + Sum[1000] + Sum[0100] + Sum[0001] - Sum[0000]
;
代码
时间是2^N
(N为tarST
里1的个数);
template< class _Type_> _Type_ ___InExPrinciple_binarySubsets( _Type_ const* _sum, int _tarST){ // `sum`的长度至少是`[tarST+1]`;
_Type_ ANS = 0;
auto flag = __builtin_popcount(_tarST)&1;
for( auto subST = _tarST; ; subST=(subST-1)&_tarST){
if( (__builtin_popcount(subST)&1) == flag){ ANS += _sum[subST];} else{ ANS -= _sum[subST];}
if(subST==0){ break;}
}
return ANS;
} // ___InExPrinciple_binarySubsets
筆記
假设全集为
S
S
S, 对于任意
N
N
N个子集
s
1
,
s
2
,
.
.
.
,
s
n
s_1, s_2, ..., s_n
s1,s2,...,sn, 求这些集合的并集
∪
\cup
∪的大小;
.
注意任意这2字, 意味着,
s
i
s_i
si可以是空集 可以是全集 可以是任何集合;
s
i
,
s
j
s_i, s_j
si,sj 他们可以相交 可以互斥 任何关系; 只要满足
s
i
∈
S
s_i \in S
si∈S即可;
容斥原理的算法是
O
(
2
n
)
O(2^n)
O(2n)的,
∣
s
1
∪
.
.
.
∪
s
n
∣
=
∑
i
∈
[
1
,
n
]
∣
s
i
∣
−
∑
i
<
j
∈
[
1
,
n
]
∣
s
i
∩
s
j
∣
+
∑
i
<
j
<
z
∈
[
1
,
n
]
∣
s
i
∩
s
j
∩
s
z
∣
−
.
.
.
\displaystyle |s_1\cup ... \cup s_n| = \sum_{i \in [1,n]} |s_i| - \sum_{i<j \in[1,n]} |s_i \cap s_j| + \sum_{i<j<z \in [1,n]} |s_i \cap s_j \cap s_z| - ...
∣s1∪...∪sn∣=i∈[1,n]∑∣si∣−i<j∈[1,n]∑∣si∩sj∣+i<j<z∈[1,n]∑∣si∩sj∩sz∣−...;
.
以单个
∣
s
i
∩
.
.
.
∣
|s_i \cap ...|
∣si∩...∣为一项, 则一共是有
2
n
−
1
2^n - 1
2n−1项;
理解容斥原理最本质的角度, 就是文氏图; 一个集合 在文氏图中的面积大小 就对应该集合的元素大小
∣
s
∣
|s|
∣s∣;
.
比如, 给定三个集合
a
,
b
,
c
a,b,c
a,b,c, 已知
∣
a
∣
=
2
,
∣
b
∣
=
3
,
∣
c
∣
=
4
|a| = 2, |b| = 3, |c| = 4
∣a∣=2,∣b∣=3,∣c∣=4, 且
∣
a
∩
b
∣
=
1
,
∣
a
∩
c
∣
=
1
,
∣
b
∩
c
∣
=
2
|a \cap b| = 1, |a \cap c| = 1, |b \cap c| = 2
∣a∩b∣=1,∣a∩c∣=1,∣b∩c∣=2, 且
∣
a
∩
b
∩
c
∣
=
1
|a \cap b \cap c| = 1
∣a∩b∩c∣=1;
.
.
那么, 我们就可以求出
a
∩
b
∩
c
a\cap b \cap c
a∩b∩c这个集合的大小, 即
∣
a
∩
b
∩
c
∣
=
(
2
+
3
+
4
)
−
(
1
+
1
+
2
)
+
(
1
)
|a \cap b \cap c| = (2 + 3 + 4) - (1 + 1 + 2) + (1)
∣a∩b∩c∣=(2+3+4)−(1+1+2)+(1);