包除原理とオイラー関数について

TL;DR

自分なりに包除原理やオイラー関数について整理しておきたかったのでメモを書きます。正確な情報は文献抄を見たほうがいいです。

文献抄

包除原理

intersectionからunionに変換する公式。

∣A1∪A2∪A3...An−1∪An∣=∑i=1n∣Ai∣+(−1)1∑1≤i≤j≤n∣Ai∩Aj∣+(−1)2∑1≤i≤j≤k≤n∣Ai∩Aj∩Ak∣...+(−1)n−1∣A1∩A2...∩An∣=∑i=1n(−1)i−1∑1≤j1≤j2...≤ji≤n∣Aj1∩Aj2∩...∩Aji∣\begin{aligned} |A_1 \cup A_2 \cup A_3 ... A_{n-1} \cup A_n| &= \sum_{i=1}^n|A_i| \\ &+ (-1)^{1}\sum_{1 \leq i \leq j \leq n}|A_i \cap A_j| \\ &+ (-1)^{2}\sum_{1 \leq i \leq j \leq k \leq n}|A_i \cap A_j \cap A_k| \\ &... \\ &+ (-1)^{n-1}|A_1 \cap A_2...\cap A_n| \\ &= \sum_{i=1}^n(-1)^{i-1}\sum_{1\leq j_1\leq j_2 ...\leq j_i \leq n}|A_{j_1} \cap A_{j_2} \cap ... \cap A_{j_i}| \end{aligned}

ただし、∣Ai∣|A_i|は集合iの要素数。

証明

正直自信がないです

∣A1∪A2∪A3...An−1∪An∣|A_1 \cup A_2 \cup A_3 ... A_{n-1} \cup A_n|に属する任意の要素xxについて考えます。左辺では、要素xxは1回のみ数え上げられます。

そこで、右辺では要素xxはプラスマイナスを考慮して、∣Aj1∩Aj2...∩Aji∣|A_{j_1} \cap A_{j_2} ... \cap A_{j_i}|において何回数え上げられたかを考えます。xxを含む集合の数をkkとし、xxを含む集合をAi(i∈k)A_i (i \in k)とします。xxを含まない集合では、カウントが行われないので考えないものとします。

Ai(i∈k)A_i (i\in k)においてカウントされる数はkC1_kC_1です。ある2つの集合Ai∩Aj(i,j∈k)A_i \cap A_j (i,j \in k)においてカウントされる数はkC2_kC_2です。同様にしてxxを含むrr個の積集合の組み合わせの中でxxがカウントされる数はkCr_kC_rです。プラスマイナスを考慮すると

∑r=1k(−1)r−1 kCr\sum_{r=1}^{k}(-1)^{r-1} \space _kC_r

とかけます。一方、二項定理より

0=(1−1)k=∑r=0k kCr(−1)r=1−∑r=1k kCr(−1)r−1\begin{aligned} 0 &= (1 - 1)^k \\ &= \sum_{r=0}^{k} \space _kC_r (-1)^r \\ &= 1 - \sum_{r=1}^{k} \space _kC_r (-1)^{r-1} \end{aligned}

となるので、任意の要素xxのカウント回数は左辺と右辺で等しいことが言えます。よって、成り立つはずです。

実装

これをメモしたかった。 bit全探索を使って書けます。

100以下の2, 3, 5の倍数について考えます。

fn main() {
    let nums = vec![2, 3, 5];
    let mut counter = 0;

    for bit in 0..1 << nums.len() {
        // 2進数のときの1の数をカウント
        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以下の互いに素な自然数の個数

任意の自然数nnについて、nnと互いに素なnn以下の自然数の数の個数を、ϕ(n)\phi(n)と書く。

このとき、nnの素因数をpip_iとすると、

ϕ(n)=n∏i=1k(1−1pi)\begin{aligned} \phi(n) = n\prod_{i=1}^{k} \left( 1 - \frac{1}{p_i} \right) \end{aligned}

が成り立つ。

包除原理による証明

やってることは正しい気がするが最後の式変形何がどうなった。

任意のx(1≤x≤k)x (1 \leq x \leq k)において、素数pip_iの倍数かつnn以下の自然数の集合を∣Ai∣|A_i|とする。このとき、∣Ai∣=npi|A_i| = \frac{n}{p_i}とかける(ex., 100以下の2の倍数は100/2個)。

また、一般に、任意の積集合において、

∣Aj1∩Aj2∩...∩Ajk∣=npj1pj2...pjk(1≤j1<j2<...<jk≤k)\begin{gathered} |A_{j_1} \cap A_{j_2} \cap ... \cap A_{j_k}| = \frac{n}{p_{j_1}p_{j_2}...p_{j_k}} \\ (1 \leq j_1 < j_2 < ... < j_k \leq k) \end{gathered}

が成り立つ(ex., 100以下の10の倍数は2の倍数かつ5の倍数なので、100/(2*5))。

ここで、和集合∣Aj1∪Aj2∪...∪Ajk∣|A_{j_1} \cup A_{j_2} \cup ... \cup A_{j_k} |は、素数pj1,pj2,...,pjkp_{j_1},p_{j_2},...,p_{j_k}の倍数を1個ずつカウントした集合であるので、

ϕ(n)=n−∣Aj1∪Aj2∪...∪Ajk∣\phi(n) = n - |A_{j_1} \cup A_{j_2} \cup ... \cup A_{j_k}|

が示せればよい。

包除原理を使うと

∣Aj1∪Aj2∪...∪Ajk∣=∑i=1k(−1)i−1∑1≤j1≤j2...≤ji≤k∣Aj1∩Aj2∩...∩Aji∣=∑i=1k(−1)i−1∑1≤j1≤j2...≤ji≤knpj1pj2...pjk=n(1−(1−1p1)(1−1p2)....(1−1pk))=n−ϕ(n)∴ϕ(n)=n−∣Aj1∪Aj2∪...∪Ajk∣\begin{aligned} |A_{j_1} \cup A_{j_2} \cup ... \cup A_{j_k}| &= \sum_{i=1}^k(-1)^{i-1}\sum_{1\leq j_1\leq j_2 ...\leq j_i \leq k}|A_{j_1} \cap A_{j_2} \cap ... \cap A_{j_i}| \\ &=\sum_{i=1}^k(-1)^{i-1}\sum_{1\leq j_1\leq j_2 ...\leq j_i \leq k}\frac{n}{p_{j_1}p_{j_2}...p_{j_k}} \\ &= n\left( 1 - \left( 1 - \frac{1}{p_1}\right)\left( 1 - \frac{1}{p_2}\right)....\left( 1 - \frac{1}{p_k}\right)\right) \\ &= n - \phi(n) \\ &\therefore \phi(n) = n - |A_{j_1} \cup A_{j_2} \cup ... \cup A_{j_k}| \end{aligned}

となり示せた。

乗法的性質を使った証明

包除原理から離れてしまうが、個人的にはこっちのほうが理解しやすかった。

参考動画

この記事に関するIssueをGithubで作成する

次に読む