Let U be the universal set, and consider n subsets A1,…,An⊂U. The sets Ai partition U into at most 2n subsets of the form (i∈I⋂Ai)∩i∈/I⋂Aic, where I⊂{1,…,n}. We will consider how to find the cardinality ∣(∩i∈IAi)∩(∩i∈/IAic)∣ of each such subset.
Eliminating ∣U∣ yields the familiar inclusion–exclusion formula that expresses a union in terms of intersections:
∣∪i=1nAi∣=I=∅∑(−1)∣I∣−1∣∩i∈IAi∣.
However, this formula follows secondarily from the fact that a union can be expressed as the complement of an intersection of complements. As the discussion below will also show, the essence of the inclusion–exclusion principle is the equation (⋆).
Now write the left-hand side of (⋆) as f(I)=∣(∩i∈IAi)∩(∩i∈/IAic)∣. Then
∣∩i∈IAi∣=J:J⊃I∑f(J)=:F(I),
and (⋆) can be written as
f(I)=J:J⊃I∑(−1)∣J∖I∣F(J).
In other words, the inclusion–exclusion principle can be interpreted as expressing each f(I) in terms of its upper cumulative sum F(I)=J:J⊃I∑f(J). As we will see in the example below, even when f(I) is difficult to compute directly, its cumulative sum F(I) may be easy to obtain. In that situation, inclusion–exclusion is one way to recover the original f(I) from the cumulative sums.
We used upper cumulative sums above, but the analogous formula for lower cumulative sums can be derived in the same way:
The universal set is U={1,…,Y}, and the N sets Ai⊂U are, with a slight abuse of notation, Ai={x∈U:x is a multiple of Ai}. Using the function f defined above, the value we seek is s=I:∣I∣=M∑f(I).
Computing each f(I) is difficult, but its cumulative sum is easy to find:
F(I)=∣∩i∈IAi∣=∣{x∈U:x is a multiple of i∈ILCM(Ai)}∣=⌊LCMi∈I(Ai)Y⌋.
If we use (⋆) to compute f(I) for every I such that ∣I∣=M, the number of computations is O((MN)2N−M). This is already fast enough to receive AC in a fast language (I succeeded in C++, but not in Python). Taking the calculation a step further, however, gives
reducing the number of terms in the sum to at most O(2N).
Sample Code (Inclusion–Exclusion)
It is acceptable to compute LCMi∈I(Ai) from scratch for each I, but the following code uses dynamic programming to improve the time complexity and reduce the amount of code.
python
from math import lcm, combN, 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)
Sample Code (Möbius Transform)
Instead of using inclusion–exclusion, we can also use the Möbius transform to find f(I) for every I, then sum the values for which ∣I∣=M.
python
from math import lcmN, 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. Divisor Inclusion–Exclusion
2.1 The Möbius Inversion Formula
Let N denote the set of positive integers, and let N0=N∪{0}. Consider the partially ordered set obtained by defining the order on N as m≤n⟺defm∣n. This poset is isomorphic to ℓc:={(e1,e2,…)∈N0N:∃i0≤∀ei=0}, ordered by e≤f⟺def∀i∈N,ei≤fi. Prime factorization gives the correspondence n=i∈N∏piei∈N↔(ei)i∈N∈ℓc.
Consider a function f(n) on N. For n∈N and a prime p, let ordp(n) be the multiplicity of the prime factor p in n, and write In={i∈N:pi∣n} (a finite set). Using the lower set Ln:={d∈N:d∣n}, we obtain the following identity of indicator functions:
Multiplying both sides by f and summing over x∈N—or, equivalently, integrating with respect to the counting measure on N weighted by f—gives
f(n)=I:I⊂In∑(−1)∣I∣F(∏i∈Ipin).(⋆⋆)
To see that this is the Möbius inversion formula, note from the definition of the Möbius functionμ that the right-hand side of (⋆⋆) equals the right-hand side of
We are given a string S of length N consisting of # and ., and must count the strings T, also of length N and consisting of # and ., whose minimum period C(T) is less than N and for which S[i] == '.' implies T[i] == '#'. If the desired count is s, then
The condition that the minimum period is d is difficult to work with. By contrast, the condition that d is a period—that the minimum period divides d—can be written simply as T[0:d] == T[d:2d] == ... == T[N-d:N]. Thus, here too, F(d) is easier to compute than f(d). Specifically,
F(d)=2∣{1≤i≤d:∀j≡imodd,S[j] == ’#’}∣.
We can therefore find s by using this expression to compute f(d). Since s=F(N)−f(N) and only one value of f is needed, either the Möbius inversion formula or differencing the cumulative sums (computing f(d) for every d∣N) will work.
Sample Code (Enumerating Divisors and Prime Factors)
Whether we use Möbius inversion or cumulative-sum differencing, we need to enumerate not only the divisors of N, but also its prime factors. We could enumerate both using trial division in O(N), but the following code reconstructs the divisors from the prime factors after enumerating them. There are several reasons for writing it this way:
When enumerating prime factors and divisors simultaneously, it feels unnatural for each i=2,…,⌊N⌋ inside the loop to serve both as a possible divisor of N and as a possible prime factor of N.
When N is composite, the loop usually terminates in fewer than ⌊N⌋ iterations.
The ordering of the divisors array is convenient.
Regarding point 3, if N=i=1∏kpiei, the divisor array D is the flattened, in-order representation of the k-dimensional array (i=1∏kpifi)0≤f1≤e1,…,0≤fk≤ek of size (e1+1)×⋯×(ek+1). The array D is not sorted by the usual numerical order, but it preserves the divisibility order—Di∣Dj⟹i≤j—and is therefore topologically sorted in the Hasse diagram. As a result, its ordering causes no problems when taking differences of cumulative sums. The ordering can also be exploited; for example, with zero-based indexing, Di∣Dj⟹DiDj=Dj−i. (Of course, the array can simply be sorted if needed, since its size is not especially large.)
python
primes = []divisors = [1]p = 2M = Nwhile p * p <= M: if M % p == 0: primes.append(p) # Alternatively: primes.append((len(divisors), p)) l = len(divisors) while M % p == 0: divisors += [d * p for d in divisors[-l:]] M //= p p += 1if M > 1: primes.append(M) # Likewise: primes.append((len(divisors), M)) divisors += [d * M for d in divisors]
Sample Code (Divisor Inclusion–Exclusion)
Rather than using the general Möbius inversion formula, we use (⋆⋆). If ω(N) denotes the number of distinct prime factors of N, the sum on the right-hand side of (⋆⋆) has 2ω(N) terms—the number of divisors for which the Möbius function is nonzero—so we iterate over only those terms. Similarly, because F(d) is not needed for every d∣N, computing only the required values reduces the time complexity to O(2ω(N)N).
The code below uses the identity s=−I⊂In,I=∅∑(−1)∣I∣F(∏i∈IpiN).
python
MOD = 119 << 23 | 1N = int(input())S = input()divisors = [1] # Enumerate only divisors of N for which the Möbius function is nonzerop = 2_N = Nwhile p * p <= _N: if _N % p == 0: divisors += [d * p for d in divisors] while _N % p == 0: _N //= p p += 1if _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)) % MODprint(ans)
Sample Code (Dynamic Programming Solution)
Above, we compute F(dN) naively for each required d∣N, but as in the inclusion–exclusion example, these values can also be found by dynamic programming. The following code does so, although I feel that its operation is less transparent in the divisor inclusion–exclusion setting. Here, F is an array of length 2ω(N), and for I∈2In≃[0,2ω(N)), F[I] equals F(∏i∈IpiN).
python
MOD = 119 << 23 | 1N = 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 = Nwhile p * p <= _N: if _N % p == 0: T += [split(t, p) for t in T] while _N % p == 0: _N //= p p += 1if _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)) % MODprint(ans)
The complexity analysis is somewhat difficult, and I am not entirely confident in it. The dynamic-programming portion appears to perform approximately Ni=1∑ω(N)j<i∏(1+pj1) computations. Using the prime number theorem,
Thus, the complexity appears to be O(Nω(N)log(ω(N))). The code performs the dynamic programming in ascending order of the primes p, but reversing that order should reduce the complexity to O(Nω(N)). This follows because log(log(pi)) in the calculation becomes log(log(pω(N)))−log(log(pω(N)−i)), together with i=1∑nlog(i)1∼log(n)n. In fact, this implementation was the fastest among my own submissions (example), and a submission produced by having Gemini translate it into C++ earned the fastest time.
Sample Code (Möbius Transform)
We could store f in an associative array and difference the cumulative sums, but the following code keeps it as an array by taking advantage of the ordering of divisors. If σ0(N) denotes the number of divisors of N, the time complexity is O(Nσ0(N)). Since 2ω(N)≤σ0(N), divisor inclusion–exclusion wins on time complexity for this example.
python
MOD = 119 << 23 | 1N = int(input())S = input()primes = []divisors = [1]p = 2_N = Nwhile 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 += 1if _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]) % MODprint(ans)
3. Generalization
I also considered extending the preceding discussion to a general partially ordered set P, although it is unclear how useful the resulting conclusion is. A poset structure alone is insufficient for the following argument; P must be given the structure of a lattice. Even then, this does not achieve the same generality as an incidence algebra.
So far, we have expressed f(x) in terms of its cumulative sum F(x). We will now go further and express the interval sum x∈[y,z]∑f(x) in terms of F(x). For an interval [y,z]⊂P, define the set of all its predecessors by
P[y,z]={x∈P∖[y,z):x<z,(x,z)⊂[y,z)}.
In particular, when y=z, this becomes Py:=P[y,y]={x∈P:x<y,(x,y)=∅}, making the meaning of “predecessor” intuitive; P[y,z] extends this notion to intervals. When P is locally finite,
[y,z]=Lz∖x∈P[y,z]⋃Lx.Proof
For ⊂, it is clear that [y,z]⊂Lz. If ∃x∈P[y,z],∃w∈[y,z],w∈Lx, then y≤w≤x<z, so x∈[y,z), contradicting x∈P[y,z]. Hence ∀x∈P[y,z],[y,z]∩Lx=∅, proving the inclusion.
Suppose, toward a contradiction, that the right-hand side is strictly larger. Then ∃w∈/[y,z],w<z,∀x∈P[y,z],w∈/Lx. We show by contradiction that ∃u∈[w,z)∩[y,z)c,u∈P[y,z]. If this were false, then
∀u∈[w,z)∩[y,z)c,∃u′∈(u,z)∩[y,z)c⊂[w,z)∩[y,z)c.
Repeating this process would construct an ascending chain w<u1<u2<⋯<un<z of arbitrary length, contradicting the finiteness of [w,z] implied by the local finiteness of P. Therefore ∃u∈[w,z)∩[y,z)c,u∈P[y,z], and in particular w∈Lu. This contradicts the original assumption that ∀x∈P[y,z],w∈/Lx.
Now give P a lattice structure and apply the indicator-function argument to the equality above:
which gives the identity μ(x,y)=S:S⊂Py,S=∅,∧S=x∑(−1)∣S∣ for the Möbius function. For a fixed y∈P, if the meet operation ∧ takes O(1) time, all values of μ(x,y) can be found in O(2∣Py∣) time.
On the other hand, because the Möbius function is the inverse of the zeta function under convolution, we can use
to find all values of μ(x,y) for a fixed y∈P in O(∣P∣2) time in general. Thus, unless ∣Py∣ is at most about O(log∣P∣), this approach does not improve the time complexity at all. The inclusion–exclusion principle, cumulative-sum differencing, and divisor inclusion–exclusion examined above do improve the complexity when expressed this way. However, since I know hardly any lattices other than these, the formula is probably best regarded as something ornamental, at least in the context of competitive programming.
Loading comments.