TL;DR
自分なりに包除原理やオイラー関数について整理しておきたかったのでメモを書きます。正確な情報は文献抄を見たほうがいいです。
文献抄
包除原理
intersection
からunion
に変換する公式。
∣A1∪A2∪A3...An−1∪An∣=i=1∑n∣Ai∣+(−1)11≤i≤j≤n∑∣Ai∩Aj∣+(−1)21≤i≤j≤k≤n∑∣Ai∩Aj∩Ak∣...+(−1)n−1∣A1∩A2...∩An∣=i=1∑n(−1)i−11≤j1≤j2...≤ji≤n∑∣Aj1∩Aj2∩...∩Aji∣
ただし、∣Ai∣は集合i
の要素数。
証明
正直自信がないです
∣A1∪A2∪A3...An−1∪An∣に属する任意の要素xについて考えます。左辺では、要素xは1回のみ数え上げられます。
そこで、右辺では要素xはプラスマイナスを考慮して、∣Aj1∩Aj2...∩Aji∣において何回数え上げられたかを考えます。xを含む集合の数をkとし、xを含む集合をAi(i∈k)とします。xを含まない集合では、カウントが行われないので考えないものとします。
Ai(i∈k)においてカウントされる数はkC1です。ある2つの集合Ai∩Aj(i,j∈k)においてカウントされる数はkC2です。同様にしてxを含むr個の積集合の組み合わせの中でxがカウントされる数はkCrです。プラスマイナスを考慮すると
r=1∑k(−1)r−1 kCr
とかけます。一方、二項定理より
0=(1−1)k=r=0∑k kCr(−1)r=1−r=1∑k kCr(−1)r−1
となるので、任意の要素xのカウント回数は左辺と右辺で等しいことが言えます。よって、成り立つはずです。
実装
これをメモしたかった。
bit
全探索を使って書けます。
100以下の2, 3, 5
の倍数について考えます。
fn main() {
let nums = vec![2, 3, 5];
let mut counter = 0;
for bit in 0..1 << nums.len() {
let popcount = bit.count_ones();
let mut mul = 1;
for i in 0..nums.len() {
if bit & 1 << nums.len() {
mul *= nums[i]
}
}
if mul == 1 {
continue;
}
if popcount % 2 == 1 {
counter += 100 / mul;
} else {
counter -= 100 / mul;
}
}
println!("{}", counter)
}
倍数性がわかるので、素因数分解すれば互いに素な数を数え上げるのとかにも使えます。
オイラー関数 自然数N以下の互いに素な自然数の個数
任意の自然数nについて、nと互いに素なn以下の自然数の数の個数を、ϕ(n)と書く。
このとき、nの素因数をpiとすると、
ϕ(n)=ni=1∏k(1−pi1)
が成り立つ。
包除原理による証明
やってることは正しい気がするが最後の式変形何がどうなった。
任意のx(1≤x≤k)において、素数piの倍数かつn以下の自然数の集合を∣Ai∣とする。このとき、∣Ai∣=pinとかける(ex., 100以下の2の倍数は100/2個)。
また、一般に、任意の積集合において、
∣Aj1∩Aj2∩...∩Ajk∣=pj1pj2...pjkn(1≤j1<j2<...<jk≤k)
が成り立つ(ex., 100以下の10の倍数は2の倍数かつ5の倍数なので、100/(2*5))。
ここで、和集合∣Aj1∪Aj2∪...∪Ajk∣は、素数pj1,pj2,...,pjkの倍数を1個ずつカウントした集合であるので、
ϕ(n)=n−∣Aj1∪Aj2∪...∪Ajk∣
が示せればよい。
包除原理を使うと
∣Aj1∪Aj2∪...∪Ajk∣=i=1∑k(−1)i−11≤j1≤j2...≤ji≤k∑∣Aj1∩Aj2∩...∩Aji∣=i=1∑k(−1)i−11≤j1≤j2...≤ji≤k∑pj1pj2...pjkn=n(1−(1−p11)(1−p21)....(1−pk1))=n−ϕ(n)∴ϕ(n)=n−∣Aj1∪Aj2∪...∪Ajk∣
となり示せた。
乗法的性質を使った証明
包除原理から離れてしまうが、個人的にはこっちのほうが理解しやすかった。
参考動画