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

TL;DR

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

文献抄

包除原理

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

A1A2A3...An1An=i=1nAi+(1)11ijnAiAj+(1)21ijknAiAjAk...+(1)n1A1A2...An=i=1n(1)i11j1j2...jinAj1Aj2...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の要素数。

証明

正直自信がないです

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

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

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

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

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

0=(11)k=r=0k kCr(1)r=1r=1k kCr(1)r1\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)=ni=1k(11pi)\begin{aligned} \phi(n) = n\prod_{i=1}^{k} \left( 1 - \frac{1}{p_i} \right) \end{aligned}

が成り立つ。

包除原理による証明

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

任意のx(1xk)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個)。

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

Aj1Aj2...Ajk=npj1pj2...pjk(1j1<j2<...<jkk)|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)

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

ここで、和集合Aj1Aj2...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)=nAj1Aj2...Ajk\phi(n) = n - |A_{j_1} \cup A_{j_2} \cup ... \cup A_{j_k}|

が示せればよい。

包除原理を使うと

Aj1Aj2...Ajk=i=1k(1)i11j1j2...jikAj1Aj2...Aji=i=1k(1)i11j1j2...jiknpj1pj2...pjk=n(1(11p1)(11p2)....(11pk))=nϕ(n)ϕ(n)=nAj1Aj2...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で作成する

Read Next