この記事の移植ですが、初学者向けの冗長な説明は省きました。

【競技プログラミング】包除原理の簡単な証明(+包除原理もメビウスの反転公式も累積和の逆だと見えるようになるまで) - Qiitaはじめに 何番煎じだよという話ですが、確率論の問題を解いていたときに(私は)絶対に忘れない包除原理の証明を思いついたので供養します。これまでも隣接代数を通して幾度となく勉強した分野ですが、公式を覚えては頭から抜け落ちるのを繰り返していました。 いくつか諸注意です: At...Qiita

1. 包除原理

1.1 証明

全体集合を UU とし、nn 個の部分集合 A1,,AnUA_1, \dots, A_n \subset U を考えます。UUAiA_i たちによって(高々) 2n2^n 個の部分集合 (iIAi)(iIAic)\displaystyle \left(\bigcap_{i \in I} A_i \right) \cap \left(\bigcap_{i \notin I} A_i^c \right) に分割されます。ただし I{1,,n}I \subset \lbrace1, \dots, n \rbrace です。これらの分割された集合に対して、その要素数 (iIAi)(iIAic)|(\cap_{i \in I} A_i) \cap (\cap_{i \notin I} A_i^c)| を求めることを考えます。

集合 AA に対しその指示関数 1A\mathbf 1_A

1A(x)={1if xA0if xA\mathbf 1_A(x) = \begin{cases} 1 \quad & \text{if } x \in A \\ 0 \quad & \text{if } x \notin A \end{cases}

とします。1AB(x)=1A(x)1B(x)\mathbf 1_{A \cap B}(x) = \mathbf 1_A(x) \mathbf 1_B(x) および 1Ac(x)=11A(x)\mathbf 1_{A^c}(x) = 1 - \mathbf 1_A(x) が成り立つことに注意すると、任意の xUx \in U に対して

1(iIAi)(iIAic)(x)=iI1Ai(x)iI1Aic(x)=iI1Ai(x)iI(11Ai(x))=iI1Ai(x)J:JI=(1)JjJ1Aj(x)=J:JI(1)JIjJ1Aj(x)=J:JI(1)JI1jJAj(x)\begin{aligned} \mathbf 1_{(\cap_{i \in I} A_i) \cap (\cap_{i \notin I} A_i^c)}(x) &= \prod_{i \in I} \mathbf 1_{A_i}(x) \prod_{i \notin I} \mathbf 1_{A_i^c}(x) \\ &= \prod_{i \in I} \mathbf 1_{A_i}(x) \prod_{i \notin I} (1 - \mathbf 1_{A_i}(x)) \\ &= \prod_{i \in I} \mathbf 1_{A_i}(x) \sum_{J: J \cap I = \emptyset} (-1)^{|J|} \prod_{j \in J} \mathbf 1_{A_j}(x) \\ &= \sum_{J: J \supset I} (-1)^{|J \setminus I|} \prod_{j \in J} \mathbf 1_{A_j}(x) \\ &= \sum_{J: J \supset I} (-1)^{|J \setminus I|} \mathbf 1_{\cap_{j \in J} A_j}(x) \end{aligned}

が成り立ちます。この両辺を xUx \in U について和をとることで

(iIAi)(iIAic)=J:JI(1)JIjJAj()|(\cap_{i \in I} A_i) \cap (\cap_{i \notin I} A_i^c)| = \sum_{J: J \supset I} (-1)^{|J\setminus I|} |\cap_{j \in J} A_j| \tag{$\star$}

が得られます。特に I=I = \emptyset とすることで

i=1nAic=I(1)IiIAi|\cap_{i=1}^n A_i^c| = \sum_{I} (-1)^{|I|} |\cap_{i\in I} A_i|

および

i=1nAic=Ui=1nAiI(1)IiIAi=U+I(1)IiIAi\begin{aligned} |\cap_{i=1}^n A_i^c| &= |U| - |\cup_{i=1}^n A_i| \\ \sum_{I} (-1)^{|I|} |\cap_{i\in I} A_i| &= |U| + \sum_{I \neq \emptyset} (-1)^{|I|} |\cap_{i \in I} A_i| \end{aligned}

より U|U| を消去して

i=1nAi=I(1)I1iIAi|\cup_{i=1}^n A_i| = \sum_{I \neq \emptyset} (-1)^{|I| - 1} |\cap_{i \in I} A_i|

という、和集合を積集合で表現するお馴染みの包除原理が得られます。しかしこれは和集合が「補集合の積集合の補集合」で表されるという性質から副次的に得られるものであり、包除原理の本質は ()(\star) の式であることが後の議論からもわかると思います。

さて、()(\star) の左辺を f(I)=(iIAi)(iIAic)f(I) = |(\cap_{i \in I} A_i) \cap (\cap_{i \notin I} A_i^c)| と記述すると

iIAi=J:JIf(J)=:F(I)|\cap_{i \in I} A_i| = \sum_{J: J \supset I} f(J) =: F(I)

となり、()(\star)

f(I)=J:JI(1)JIF(J)f(I) = \sum_{J: J \supset I} (-1)^{|J \setminus I|} F(J)

と記述できます。つまり各 f(I)f(I) の値を、その(上側)累積和 F(I)=J:JIf(J)F(I) = \displaystyle \sum_{J: J \supset I} f(J) を用いて表現したものが包除原理であると解釈できます。以下の例題でも見るように、f(I)f(I) の値を求めることは難しくても累積和 F(I)F(I) の値は簡単に求まるとき、累積和から元の f(I)f(I) を復元する方法の 1 つが包除原理となります。

上記では上側累積和を取りましたが、下側累積和を取る式も同様に考えることができます。

1(iIAi)(iIAic)(x)=iI1Ai(x)iI1Aic(x)=iI(11Aic(x))iI1Aic(x)=J:JI(1)JjJ1Ajc(x)iI1Aic(x)=J:JIc(1)JIcjJ1Ajc(x)=J:JIc(1)JIc1jJAjc(x)=J:JIc(1)JIc(11jJAj(x))=J:JIc(1)JIc11jJAj(x)(J:JIc(1)JIc=0)\begin{aligned} & \mathbf 1_{(\cap_{i \in I} A_i) \cap (\cap_{i \notin I} A_i^c)}(x) \\ = & \prod_{i \in I} \mathbf 1_{A_i}(x) \prod_{i \notin I} \mathbf 1_{A_i^c}(x) \\ = & \prod_{i \in I} (1 - \mathbf 1_{A_i^c}(x)) \prod_{i \notin I} \mathbf 1_{A_i^c}(x) \\ = & \sum_{J: J \subset I} (-1)^{|J|} \prod_{j \in J} \mathbf 1_{A_j^c}(x) \prod_{i \notin I} \mathbf 1_{A_i^c}(x) \\ = & \sum_{J: J \supset I^c} (-1)^{|J \setminus I^c|} \prod_{j \in J} \mathbf 1_{A_j^c}(x) \\ = & \sum_{J: J \supset I^c} (-1)^{|J \setminus I^c|} \mathbf 1_{\cap_{j \in J} A_j^c}(x) \\ = & \sum_{J: J \supset I^c} (-1)^{|J \setminus I^c|} (1 - \mathbf 1_{\cup_{j \in J} A_j}(x)) \\ = & \sum_{J: J \supset I^c} (-1)^{|J \setminus I^c| - 1} \mathbf 1_{\cup_{j \in J} A_j}(x) \quad (\because \sum_{J: J \supset I^c} (-1)^{|J \setminus I^c|} = 0) \end{aligned}

より、特に I={1,,n}I = \lbrace 1, \dots, n \rbrace としたとき

i=1nAi=I(1)I1iIAi|\cap_{i = 1}^n A_i| = \sum_{I \neq \emptyset} (-1)^{|I| - 1} |\cup_{i \in I} A_i|

という、積集合を和集合で表現するという実用性のよくわからない包除原理の公式が得られます。

1.2 例題

ABC423-F Loud Cicada

全体集合は U={1,,Y}U = \lbrace 1, \dots, Y \rbraceNN 個の集合 AiUA_i \subset U は(記号の濫用ですが) Ai={xU:x は Ai の倍数}A_i = \lbrace x \in U : x \text{ は } A_i \text{ の倍数} \rbrace となります。上記の ff を用いると、求める値は s=I:I=Mf(I)s = \displaystyle \sum_{I: |I| = M} f(I) となります。

個々の f(I)f(I) を求めるのは困難ですが、その累積和 F(I)F(I) については

F(I)=iIAi={xU:x は LCMiI(Ai) の倍数 }=YLCMiI(Ai)F(I) = |\cap_{i \in I} A_i| = |\lbrace x \in U : \text{$x$ は $ \displaystyle \operatorname*{LCM}_{i \in I}(A_i)$ の倍数 } \rbrace| = \left\lfloor \dfrac{Y}{\operatorname*{LCM}_{i \in I}(A_i)} \right\rfloor

となり簡単に求まります。全ての I=M|I| = M なる II に対して ()(\star) を用いて f(I)f(I) を計算しても計算回数は O((NM)2NM)\displaystyle O\left(\binom{N}{M}2^{N-M} \right) となり、このままでも高速な言語では AC できます(C++ なら通せたが Python では通せなかった)が、さらに計算を進めると

s=I:I=MJ:JI(1)JMF(J)=J:JM(1)JMF(J)I:IJ,I=M1=J:JM(1)JM(JM)F(J)\begin{aligned} s &= \sum_{I: |I| = M} \sum_{J: J \supset I} (-1)^{|J| - M} F(J) \\ &= \sum_{J: |J| \ge M} (-1)^{|J| - M} F(J) \sum_{I: I \subset J, |I| = M}1 \\ &= \sum_{J: |J| \ge M} (-1)^{|J| - M} \binom{|J|}{M} F(J) \end{aligned}

となり、和の項数を高々 O(2N)O(2^N) にまで抑えることができます。

サンプルコード(包除原理)

II ごとに LCMiI(Ai)\operatorname*{LCM}_{i \in I}(A_i) の値をその都度求めても問題ないですが、以下では計算量改善および記述量減少のため動的計画法で求めています。

python
from math import lcm, comb

N, M, Y = map(int, input().split())
A = list(map(int, input().split()))

LCM = [1]
for a in A:
    LCM += [min(lcm(l, a), Y + 1) for l in LCM]

F = [Y // l for l in LCM]
ans = sum(pow(-1, b - M & 1) * comb(b, M) * v for I, v in enumerate(F) if (b := I.bit_count()) >= M)
print(ans)

サンプルコード(メビウス変換)

包除原理ではなくメビウス変換を用いて全ての II に対して f(I)f(I) を求めることもできます。その後 I=M|I| = M なる II について足し合わせれば良いです。

python
from math import lcm

N, M, Y = map(int, input().split())
A = list(map(int, input().split()))

LCM = [1]
for a in A:
    LCM += [min(lcm(l, a), Y + 1) for l in LCM]

f = [Y // l for l in LCM]
for i in range(N):
    for I in range(1 << N):
        if I >> i & 1:
            f[I ^ 1 << i] -= f[I]

ans = sum(v for I, v in enumerate(f) if I.bit_count() == M)
print(ans)

2. 約数包除

2.1 メビウスの反転公式

正の整数全体を N\mathbb N で表し、N0=N{0}\mathbb N_0 = \mathbb N \cup \lbrace 0 \rbrace とします。N\mathbb N の順序を mn    defmnm \le n \overset{\mathrm{def}}{\iff} m \mid n とした半順序集合を考えるのですが、これは c:={(e1,e2,)N0N:i0i,ei=0}\ell_c := \lbrace (e_1, e_2, \dots) \in \mathbb N_0^{\mathbb N} : \exists i_0 \le \forall i, \, e_i = 0 \rbrace に、その順序を ef    defiN,eifie \le f \overset{\mathrm{def}}{\iff} \forall i \in \mathbb N, \, e_i \le f_i とした半順序集合と同型になります。そしてその対応は素因数分解を用いて n=iNpieiN(ei)iNcn = \displaystyle \prod_{i \in \mathbb N} p_i^{e_i} \in \mathbb N \leftrightarrow (e_i)_{i \in \mathbb N} \in \ell_c となります。

N\mathbb N 上の関数 f(n)f(n) を考えます。nNn \in \mathbb N と素数 pp に対して、nn に含まれる素因数 pp の個数を ordp(n)\operatorname*{ord}_p(n) とし、In={iN:pin}I_n = \lbrace i \in \mathbb N : p_i \mid n \rbrace と表すことにします(これは有限集合です)。下側集合 Ln:={dN:dn}L_n := \lbrace d \in \mathbb N : d \mid n \rbrace を使って指示関数を考えると

1{n}(x)=iN1ordpi(x)=ei=iIn1ordpi(x)=0iIn(1ordpi(x)ei1ordpi(x)ei1)=IIn(1)I1Ln/iIpi(x)\begin{aligned} \mathbf 1_{\lbrace n \rbrace}(x) &= \prod_{i \in \mathbb N} \mathbf 1_{\operatorname*{ord}_{p_i}(x) = e_i} \\ &= \prod_{i \notin I_n} \mathbf 1_{\operatorname*{ord}_{p_i}(x) = 0} \prod_{i \in I_n} (\mathbf 1_{\operatorname*{ord}_{p_i}(x) \le e_i} - \mathbf 1_{\operatorname*{ord}_{p_i}(x) \le e_i-1} ) \\ &= \sum_{I \subset I_n} (-1)^{|I|} \mathbf 1_{L_{n/\prod_{i \in I} p_i}}(x) \end{aligned}

となります。辺々に ff を掛けて xNx \in \mathbb N について和を取る(あるいは同じことですが ff の重みを持つ N\mathbb N 上の数え上げ測度で積分する)ことで

f(n)=I:IIn(1)IF(niIpi)()f(n) = \sum_{I: I \subset I_n} (-1)^{|I|}F\left(\frac{n}{\prod_{i \in I} p_i}\right) \tag{$\star\star$}

が得られます。これがメビウスの反転公式であることは、メビウス関数 μ\mu の定義を考慮すると

f(n)=dnμ(nd)F(d)=dnμ(d)F(nd)f(n) = \sum_{d \mid n} \mu \left(\dfrac{n}{d} \right) F(d) = \sum_{d \mid n} \mu(d) F\left( \frac{n}{d} \right)

の右辺と ()(\star\star) の右辺が等しいことがわかると思います。

2.2 例題

ABC304-F Shift Table

#. からなる長さ NN の文字列 SS が与えられるので、同じく #. からなる長さ NN の文字列 TT であって、その最小周期 C(T)C(T)NN より小さく、かつ S[i] == '.' ならば T[i] == '#' となるものの個数を数える問題です。求める数を ss とすると、

s=dN,dNT:C(T)=d1i,S[i] == ’.’    T[i] == ’#’=:dN,dNf(d)\begin{aligned} s &= \sum_{d \mid N, d \neq N} \sum_{T: C(T) = d} \mathbf 1_{\forall i, \, \text{S[i] == '.'} \implies \text{T[i] == '\#'}} \\ &=: \sum_{d \mid N, d \neq N} f(d) \end{aligned}

となります。最小周期が dd であるという条件は扱いづらいですが、周期が dd である(最小周期が dd の約数である)という条件は T[0:d] == T[d:2d] == ... == T[N-d:N] と扱いやすく、ここでも f(d)f(d) より F(d)F(d) の方が求めやすいです。具体的には

F(d)=2{1id:jimodd,S[j] == ’#’}F(d) = 2^{| \lbrace 1 \le i \le d : \forall j \equiv i \mod d, \, \text{S[j] == '\#'} \rbrace|}

なので、これを用いて f(d)f(d) を計算することで ss が求まります。なお s=F(N)f(N)s = F(N) - f(N) であり必要な ff の値は 1 つだけなので、メビウスの反転公式を用いる方法でも累積和の差分を取る(全ての dNd \mid N に対して f(d)f(d) を計算する)方法でも問題ありません。

サンプルコード(約数列挙と素因数列挙)

メビウスの反転公式でも累積和の差分でもそうなのですが、NN の約数を列挙するだけでなく、NN に含まれる素因数も列挙したいです。O(N)O(\sqrt N) の試し割り法で素因数列挙と約数列挙を双方行うのでもよいのですが、以下のコードでは素因数を列挙した後にその素因数から約数を復元しています。このようなコードを書いた理由はいくつかあり:

  1. 素因数列挙と約数列挙を同時に行う場合、ループの内部で i=2,,Ni = 2, \dots, \lfloor \sqrt N\rfloor に対して NN の約数の役割と NN の素因数の役割の両方を担わせることに違和感がある
  2. NN が合成数の場合、大抵 N\lfloor \sqrt N\rfloor 回未満のループで停止する
  3. divisors 配列の順序が扱いやすい

3 について、N=i=1kpiei\displaystyle N = \prod_{i=1}^k p_i^{e_i} とすると divisors 配列 DD は大きさ (e1+1)××(ek+1)(e_1 + 1) \times \dots \times (e_k + 1)kk 次元配列 (i=1kpifi)0f1e1,,0fkek\displaystyle\left(\prod_{i=1}^k p_i^{f_i}\right)_{0 \le f_1 \le e_1, \dots, 0 \le f_k \le e_k} をこの順序で 1 次元化 (flatten) したものになっています。DD は通常の大小関係でソートされていませんが、DiDj    ijD_i \mid D_j \implies i \le j と約数の順序を保っている(ハッセ図においてトポロジカルソートされている)ため累積和の差分を考える上で順序が問題にならないだけでなく、例えば 0-indexed として DiDj    DjDi=DjiD_i \mid D_j \implies \dfrac{D_j}{D_i} = D_{j-i} が成り立つなどこの順序を生かすこともできます(たいした計算量でもないので必要であればソートすればよいです)。

python
primes = []
divisors = [1]
p = 2
M = N
while p * p <= M:
    if M % p == 0:
        primes.append(p)  # または primes.append((len(divisors), p))
        l = len(divisors)
        while M % p == 0:
            divisors += [d * p for d in divisors[-l:]]
            M //= p
    p += 1
if M > 1:
    primes.append(M)  # 同様に primes.append((len(divisors), M))
    divisors += [d * M for d in divisors]

サンプルコード(約数包除)

一般的なメビウスの反転公式ではなく ()(\star\star) の方を使います。NN に含まれる素因数の種類数を ω(N)\omega(N) とすると、()(\star\star) の右辺の和は 2ω(N)2^{\omega(N)} 個(=メビウス関数が 00 でない値を取る個数)なので、この部分だけをイテレートして和を計算します。同様に F(d)F(d) の値も全ての dNd \mid N で必要なわけではないので、必要な部分だけを計算することで計算量を O(2ω(N)N)O(2^{\omega(N)}N) に抑えられます。 以下のコードでは s=IIn,I(1)IF(NiIpi)s = -\displaystyle \sum_{I \subset I_n, I \neq \emptyset} (-1)^{|I|} F\left(\dfrac{N}{\prod_{i \in I}p_i}\right) であることを利用して計算しています。

python
MOD = 119 << 23 | 1

N = int(input())
S = input()

divisors = [1]  # メビウス関数が 0 でない N の約数のみを列挙
p = 2
_N = N
while p * p <= _N:
    if _N % p == 0:
        divisors += [d * p for d in divisors]
        while _N % p == 0:
            _N //= p
    p += 1
if _N > 1:
    divisors += [d * _N for d in divisors]

def F(d):
    count = sum(all(S[j] == '#' for j in range(i, N, d)) for i in range(d))
    return pow(2, count, MOD)

ans = -sum(pow(-1, I.bit_count() & 1) * F(N // d) for I, d in enumerate(divisors[1:], 1)) % MOD
print(ans)

サンプルコード(動的計画法解)

上記では必要な各 dNd \mid N に対して愚直に F(Nd)F \left(\dfrac{N}{d} \right) の値を計算していますが、包除原理の例題のように動的計画法でも求められます。以下のコードはそれを実現したものですが、約数包除だと何をしているのかわかりにくい気がします。コード内の F は長さ 2ω(N)2^{\omega(N)} の配列であり、I2In[0,2ω(N))I \in 2^{I_n} \simeq [0, 2^{\omega(N)}) に対し、F[I] の値は F(NiIpi)F \left(\dfrac{N}{\prod_{i \in I} p_i} \right) となります。

python
MOD = 119 << 23 | 1

N = int(input())
S = input()

def split(t, d):
    l = len(t)
    return ''.join('#' if all(t[j] == '#' for j in range(i, l, l // d)) else '.' for i in range(l // d))

T = [S]
p = 2
_N = N
while p * p <= _N:
    if _N % p == 0:
        T += [split(t, p) for t in T]
        while _N % p == 0:
            _N //= p
    p += 1
if _N > 1:
    T += [split(t, _N) for t in T]

F = [pow(2, t.count('#'), MOD) for t in T]
ans = -sum(pow(-1, I.bit_count() & 1) * v for I, v in enumerate(F[1:], 1)) % MOD
print(ans)

計算量解析がちょっと大変で、あまり自信がありません。動的計画法の部分で Ni=1ω(N)j<i(1+1pj)\displaystyle N \sum_{i=1}^{\omega(N)} \prod_{j < i} \left(1 + \dfrac{1}{p_j} \right) 回程度の計算を行っているように見受けられます。素数定理を用いると

i=1ω(N)j<i(1+1pj)i=1ω(N)j<iexp(1pj)=i=1ω(N)exp(j<i1pj)i=1ω(N)exp(log(log(pi)))=i=1ω(N)log(pi)i=1ω(N)log(ilog(i))i=1ω(N)log(i)ω(N)log(ω(N))\begin{aligned} \sum_{i=1}^{\omega(N)} \prod_{j < i} \left(1 + \dfrac{1}{p_j} \right) & \sim \sum_{i=1}^{\omega(N)} \prod_{j < i} \exp \left( \dfrac{1}{p_j} \right) \\ &= \sum_{i=1}^{\omega(N)} \exp \left( \sum_{j < i} \dfrac{1}{p_j} \right) \\ & \sim \sum_{i=1}^{\omega(N)} \exp( \log (\log (p_i))) \\ &= \sum_{i=1}^{\omega(N)} \log(p_i) \\ & \sim \sum_{i=1}^{\omega(N)} \log(i \log (i)) \\ & \sim \sum_{i=1}^{\omega(N)} \log(i) \\ & \sim \omega(N) \log(\omega(N)) \end{aligned}

なので O(Nω(N)log(ω(N)))O(N \omega(N) \log (\omega(N))) となりそうです。そしてコードでは素数 pp の昇順で動的計画法を行なっていますが、pp の降順にすることで O(Nω(N))O(N \omega(N)) に落とせるはずです(計算内の log(log(pi))\log (\log (p_i))log(log(pω(N)))log(log(pω(N)i))\log(\log(p_{\omega(N)})) - \log(\log(p_{\omega(N) - i})) に変わるのと i=1n1log(i)nlog(n)\displaystyle \sum_{i=1}^{n} \dfrac{1}{\log (i)} \sim \dfrac{n}{\log (n)} より)。実際に私の提出の中でもこの実装が最速で(実装例)、これを Gemini に C++ へ変換してもらった提出は fastest を獲得しました。

サンプルコード(メビウス変換)

f を連想配列にして累積和の差分を取ることもできますが、以下では divisors 配列の順序を利用して配列のまま行っています。計算量は NN の約数の個数を σ0(N)\sigma_0(N) として O(Nσ0(N))O(N \sigma_0 (N)) となります。2ω(N)σ0(N)2^{\omega(N)} \le \sigma_0 (N) なので計算量においてはこの例題では約数包除に軍配が上がります。

python
MOD = 119 << 23 | 1

N = int(input())
S = input()

primes = []
divisors = [1]
p = 2
_N = N
while p * p <= _N:
    if _N % p == 0:
        primes.append((l := len(divisors), p))
        while _N % p == 0:
            divisors += [d * p for d in divisors[-l:]]
            _N //= p
    p += 1
if _N > 1:
    primes.append((len(divisors), _N))
    divisors += [d * _N for d in divisors]

def F(d):
    count = sum(all(S[j] == '#' for j in range(i, N, d)) for i in range(d))
    return pow(2, count, MOD)

f = [F(d) for d in divisors]
for i, p in primes:
    for j in range(len(divisors) - 1, -1, -1):
        if divisors[j] % p == 0:
            f[j] -= f[j - i]

ans = (F(N) - f[-1]) % MOD
print(ans)

3. 一般化

ここまでの議論を一般の半順序集合 PP に拡張することも考えたのですが、得られた帰結がどこまで有用なのかは謎です。以下の議論において PP は半順序集合では不十分のため (lattice) の構造を入れる必要があり、この時点で隣接代数ほどの一般性は得られておりません。

今までは f(x)f(x) をその累積和 F(x)F(x) で表現してきましたが、ここではさらに区間和 x[y,z]f(x)\displaystyle \sum_{x \in [y,z]} f(x)F(x)F(x) で表すことを考えます。区間 [y,z]P[y, z] \subset P に対し、その直前要素 (predecessor) 全体を

P[y,z]={xP[y,z):x<z,(x,z)[y,z)}P_{[y,z]} = \lbrace x \in P \setminus [y,z) : x < z, \, (x,z) \subset [y,z) \rbrace

とします。特に y=zy=z のときは Py:=P[y,y]={xP:x<y,(x,y)=}P_y := P_{[y,y]} = \lbrace x \in P : x < y , \, (x, y) = \emptyset \rbrace となり直前の意味が直感的に理解でき、P[y,z]P_{[y,z]} はこれを区間に拡張したものです。PP が局所有限のとき

[y,z]=LzxP[y,z]Lx[y, z] = \displaystyle L_z \setminus \bigcup_{x \in P_{[y,z]}} L_x

が成り立ちます。

証明

\subset について、[y,z]Lz[y,z] \subset L_z は明らか。xP[y,z],w[y,z],wLx\exists x \in P_{[y,z]}, \, \exists w \in [y,z], \, w \in L_x とすると ywx<zy \le w \le x < z となり x[y,z)x \in [y, z) となるが、これは xP[y,z]x \in P_{[y,z]} に矛盾する。よって xP[y,z],[y,z]Lx=\forall x \in P_{[y,z]}, \, [y,z] \cap L_x = \emptyset より包含関係が成り立つ。

右辺が真に大きいとして矛盾を導く。仮定より w[y,z],w<z,xP[y,z],wLx\exists w \notin [y,z], \, w < z, \, \forall x \in P_{[y,z]}, \, w \notin L_x となる。このとき u[w,z)[y,z)c,uP[y,z]\exists u \in [w,z)\cap[y,z)^c, \, u \in P_{[y,z]} となることを背理法で示す。これが成り立たないとすると

u[w,z)[y,z)c,u(u,z)[y,z)c[w,z)[y,z)c\forall u \in [w, z) \cap [y, z)^c, \, \exists u' \in (u, z) \cap [y,z)^c \subset [w,z) \cap [y,z)^c

となり、これを繰り返すことで w<u1<u2<<un<zw < u_1 < u_2 < \dots < u_n < z と任意の長さの昇鎖を作れることになるが、これは PP の局所有限性より [w,z][w, z] が有限集合であることに矛盾。従って u[w,z)[y,z)c,uP[y,z]\exists u \in [w,z)\cap[y,z)^c, \, u \in P_{[y,z]} であり、特に wLuw \in L_u となるが、これは最初の仮定であった xP[y,z],wLx\forall x \in P_{[y,z]}, \, w \notin L_x に矛盾する。

ここで PP に束の構造を導入し、上記の等式に指示関数の議論を適用すると

1[y,z]=1LzxP[y,z](11Lx)=1Lz+S:SP[y,z],S(1)SxS1Lx=1Lz+S:SP[y,z],S(1)S1LS\begin{aligned} \mathbf 1_{[y,z]} &= \mathbf 1_{L_z} \prod_{x \in P_{[y,z]}} \mathbf (1 - \mathbf 1_{L_x}) \\ &= \mathbf 1_{L_z} + \sum_{S: S \subset P_{[y,z]}, S \neq \emptyset} (-1)^{|S|} \prod_{x \in S} \mathbf 1_{L_x} \\ &= \mathbf 1_{L_z} + \sum_{S: S \subset P_{[y,z]}, S \neq \emptyset} (-1)^{|S|} \mathbf 1_{L_{\wedge S}} \end{aligned}

となり、辺々を ff の重みをつけた数え上げ測度で積分することで

x[y,z]f(x)=F(z)+S:SP[y,z],S(1)SF(S)=F(z)+x[P[y,z],z)(S:SP[y,z],S,S=x(1)S)F(x)\begin{aligned} \sum_{x \in [y,z]}f(x) &= F(z) + \sum_{S: S\subset P_{[y,z]}, S\neq \emptyset} (-1)^{|S|} F(\wedge S) \\ &= F(z) + \sum_{x \in [\wedge P_{[y,z]}, z)} \left( \sum_{S: S\subset P_{[y,z]}, S\neq \emptyset, \wedge S = x} (-1)^{|S|} \right) F(x) \end{aligned}

が得られます。特に y=zy=z のとき

f(y)=F(y)+x[Py,y)(S:SPy,S,S=x(1)S)F(x)f(y) = F(y) + \sum_{x \in [\wedge P_y, y)} \left( \sum_{S: S \subset P_y, S \neq \emptyset, \wedge S = x} (-1)^{|S|} \right) F(x)

となり、メビウス関数に関する等式 μ(x,y)=S:SPy,S,S=x(1)S\mu(x,y) = \displaystyle \sum_{S: S\subset P_y, S\neq \emptyset, \wedge S = x} (-1)^{|S|} が得られます。固定した yPy \in P に対して全ての μ(x,y)\mu(x,y) の値を求める計算量は、\wedge の計算量が O(1)O(1) のとき O(2Py)O(2^{|P_y|}) です。

一方でメビウス関数が畳み込みにおけるゼータ関数の逆元であることから

μ(x,y)={1if x=yz(x,y]μ(z,y)if x<y0otherwise\mu(x,y) = \begin{cases} 1 \quad & \text{if } x=y \\ -\displaystyle \sum_{z \in (x, y]} \mu(z, y) \quad & \text{if } x < y \\ 0 \quad & \text{otherwise} \end{cases}

を用いると、固定した yPy \in P に対して全ての μ(x,y)\mu(x,y) の値を求める計算量は一般に O(P2)O(|P|^2) なので、少なくとも Py=O(logP)|P_y| = O(\log |P|) 程度まで小さくないと何の計算量改善にもなっていないことがわかります。ここまでで確認した包除原理、累積和の差分、および約数包除はこれを用いることで計算量が改善されていますが、そもそもそれ以外の束を全然知らないので、少なくとも競技プログラミングの文脈においては観賞用の式なのでしょう。