Back to Number Theory Dsa

Möbius Inversion for Coprime Counting

September 22, 2026 Wasil Zafar 9 min read

Convert gcd constraints into divisor sums.

Table of Contents

  1. Intuition, Vocabulary and Foundations
  2. Step-by-Step Derivations and Algorithms
  3. Implementation, Complexity and Engineering
  4. Applications, Case Studies and Exercises

Intuition, Vocabulary and Foundations

A gcd condition couples several values, which makes direct counting awkward. Möbius inversion replaces that global condition with a signed sum of simpler divisibility conditions. Instead of asking whether two values have gcd $1$, count how many are divisible by each $d$, then combine those counts with the Möbius function.

The central identity is

$$ \mathbf 1_{\gcd(x_1,\ldots,x_k)=1} = \sum_{d\mid\gcd(x_1,\ldots,x_k)}\mu(d). $$

The divisor sum equals $1$ when the gcd is $1$ and $0$ otherwise because $\sum_{d\mid n}\mu(d)=\mathbf1_{n=1}$. This cancellation resembles inclusion–exclusion over prime divisors. Terms with $\mu(d)=1$ add, terms with $\mu(d)=-1$ subtract, and terms with $\mu(d)=0$ ignore divisors containing a squared prime.

For a multiset of positive integers, let $C_d=\#\{i:d\mid a_i\}$. There are $\binom{C_d}{2}$ unordered index pairs whose values are both divisible by $d$. Thus the number of unordered coprime index pairs is $\sum_{d\ge1}\mu(d)\binom{C_d}{2}$. For ordered $k$-tuples from $\{1,\ldots,n\}$ with repetition, the corresponding count is $\sum_{d=1}^{n}\mu(d)\lfloor n/d\rfloor^k$. The combinatorial model determines the inner count.

ConceptDefinition or identityConcrete exampleNon-example or boundaryQuestion answered
Möbius function$μ(n)=0$ for a squared prime factor; otherwise $(-1)^r$ for $r$ distinct primes$μ(30)=-1$, $μ(12)=0$It is not parity of the integerWhat sign does divisor $n$ contribute?
Cancellation identity$\sum_{d\mid n}\mu(d)=\mathbf1_{n=1}$For $n=6$, the signed divisor sum is $0$Summing over nondivisors breaks the identityHow is gcd equality converted to a divisor sum?
Divisor frequency$C_d$ counts inputs divisible by $d$In $[2,3,4,6]$, $C_2=3$It does not count gcd exactly $d$How many objects satisfy one divisibility condition?
Unordered pair count$\binom{C_d}{2}$Three divisible entries create three pairs$C_d^2$ counts a different modelWhich count matches distinct unordered indices?
Ordered tuple count$\lfloor n/d\rfloor^k$Multiples of $d$ fill all $k$ positionsRepeated values are allowedHow many tuples are coordinatewise divisible by $d$?
Exact gcd countScale by the target gcd or invert divisor totalsCount gcd $g$ through multiples of $g$Coprime counting alone isolates only $1$How is a specified gcd isolated?
Mertens prefix$M(x)=\sum_{d\le x}\mu(d)$Supplies block sums of $\mu$It is not the point value $\mu(x)$How are quotient-equal ranges aggregated?
Quotient grouping$\lfloor n/d\rfloor$ is constant on an intervalA block ends at $\lfloor n/q\rfloor$Advancing one divisor at a time misses the speedupHow can the sum use $O(\sqrt n)$ blocks?

Step-by-Step Derivations and Algorithms

For an unordered index pair $(i,j)$, insert the indicator identity:

$$ \mathbf1_{\gcd(a_i,a_j)=1} = \sum_{d\mid a_i,\ d\mid a_j}\mu(d). $$

Summing over pairs and exchanging sums gives

$$ \sum_{i\lt j}\mathbf1_{\gcd(a_i,a_j)=1} = \sum_{d\ge1}\mu(d) \sum_{\substack{i\lt j\\d\mid a_i,\ d\mid a_j}}1 = \sum_{d\ge1}\mu(d)\binom{C_d}{2}. $$

Every pair with gcd $g$ receives total weight $\sum_{d\mid g}\mu(d)$, which is one only for $g=1$. To compute all $C_d$ for values bounded by $V$, build an occurrence array and sum over multiples. The number of visits is $\sum_{d=1}^{V}\lfloor V/d\rfloor=O(V\log V)$.

For $\sum_{d\le n}\mu(d)\lfloor n/d\rfloor^k$, precompute the Mertens prefix. If $q=\lfloor n/l\rfloor$, the quotient remains constant through $r=\lfloor n/q\rfloor$, and the block contributes $(M(r)-M(l-1))q^k$. Only $O(\sqrt n)$ distinct quotients occur.

To count gcd exactly $g$, divide eligible values by $g$ and count coprime objects in the scaled domain, or recover all exact-gcd counts by descending divisor inversion. The best approach depends on whether there is one target or an entire gcd distribution.

Möbius Inversion for Coprime Counting algorithm map
flowchart TD
    A["Define ordered or unordered objects"] --> B["Replace gcd condition by divisor indicator"]
    B --> C{"Explicit multiset?"}
    C -->|Yes| D["Build value frequencies"]
    D --> E["Accumulate counts over multiples"]
    E --> F["Apply mu[d] times combinatorial count"]
    C -->|No, dense interval| G["Precompute mu and Mertens prefix"]
    G --> H{"Repeated floor quotients?"}
    H -->|Yes| I["Group quotient intervals"]
    H -->|No| J["Sum directly"]
    F --> K["Verify against gcd oracle"]
    I --> K
    J --> K

For each object with gcd $g$, the algorithm assigns total weight $\sum_{d\mid g}\mu(d)=\mathbf1_{g=1}$, proving exactness. Divisor-frequency preprocessing costs $O(V\log V)$ time and $O(V)$ space for maximum value $V$; after Möbius-prefix preprocessing, a dense floor-quotient sum uses $O(\sqrt n)$ blocks.

Implementation, Complexity and Engineering

The reference implementation computes $\mu$ with a linear sieve, counts unordered coprime index pairs in a positive-integer multiset, and counts ordered coprime tuples from a dense interval. Independent brute-force functions verify both counting models.

Python integers prevent count overflow. In fixed-width languages, $\binom{C_d}{2}$ may require 64 bits even when values use 32 bits, and $\lfloor n/d\rfloor^k$ grows rapidly. Use checked wide arithmetic or reduce modulo a specified output modulus.

from itertools import product
from math import gcd


def mobius_linear(limit):
    if limit < 1:
        raise ValueError("limit must be at least 1")
    primes = []
    smallest = [0] * (limit + 1)
    mu = [0] * (limit + 1)
    mu[1] = 1
    for value in range(2, limit + 1):
        if smallest[value] == 0:
            smallest[value] = value
            primes.append(value)
            mu[value] = -1
        for prime in primes:
            composite = value * prime
            if composite > limit:
                break
            smallest[composite] = prime
            if prime == smallest[value]:
                mu[composite] = 0
                break
            mu[composite] = -mu[value]
    return mu


def coprime_unordered_pairs(values):
    if any(value <= 0 for value in values):
        raise ValueError("all values must be positive")
    if len(values) < 2:
        return 0
    maximum = max(values)
    frequency = [0] * (maximum + 1)
    for value in values:
        frequency[value] += 1
    mu = mobius_linear(maximum)
    answer = 0
    for divisor in range(1, maximum + 1):
        divisible = sum(
            frequency[multiple]
            for multiple in range(divisor, maximum + 1, divisor)
        )
        answer += mu[divisor] * divisible * (divisible - 1) // 2
    return answer


def mertens_prefix(limit):
    mu = mobius_linear(limit)
    prefix = [0] * (limit + 1)
    for value in range(1, limit + 1):
        prefix[value] = prefix[value - 1] + mu[value]
    return mu, prefix


def ordered_coprime_tuples(n, arity):
    if n < 1 or arity < 1:
        raise ValueError("n and arity must be positive")
    _, mertens = mertens_prefix(n)
    answer = 0
    left = 1
    while left <= n:
        quotient = n // left
        right = n // quotient
        answer += (
            mertens[right] - mertens[left - 1]
        ) * quotient**arity
        left = right + 1
    return answer


def brute_unordered_pairs(values):
    return sum(
        gcd(values[i], values[j]) == 1
        for i in range(len(values))
        for j in range(i + 1, len(values))
    )


def tuple_gcd(items):
    result = 0
    for item in items:
        result = gcd(result, item)
    return result


def brute_ordered_tuples(n, arity):
    return sum(
        tuple_gcd(items) == 1
        for items in product(range(1, n + 1), repeat=arity)
    )


sample = [2, 3, 4, 6]
assert coprime_unordered_pairs(sample) == 2
assert coprime_unordered_pairs(sample) == brute_unordered_pairs(sample)

with_duplicates = [2, 2, 3, 3, 6, 7]
assert coprime_unordered_pairs(with_duplicates) == brute_unordered_pairs(with_duplicates)

for maximum in range(1, 11):
    values = list(range(1, maximum + 1))
    assert coprime_unordered_pairs(values) == brute_unordered_pairs(values)

for n in range(1, 9):
    for arity in (1, 2, 3):
        assert ordered_coprime_tuples(n, arity) == brute_ordered_tuples(n, arity)

mu = mobius_linear(12)
assert mu[1:13] == [1, -1, -1, 0, -1, 1, -1, 0, 0, 1, -1, 0]

try:
    coprime_unordered_pairs([0, 1, 2])
    raise AssertionError("zero input was accepted")
except ValueError:
    pass

print("all tests passed")
Implementation choiceCounting modelComplexityNumeric riskFailure modeMitigation
Pairwise gcd scanUnordered index pairs$O(N^2\log V)$Pair count may exceed 32 bitsToo slow for large $N$Keep as a small oracle
Divisor-frequency inversionUnordered index pairs$O(V\log V+N)$$\binom N2$ needs a wide typeLosing multiplicitiesBuild frequencies by index multiplicity
Ordered pair formulaModel-dependentSimilar preprocessingCount near $N^2$Reusing the unordered formulaDerive from the object definition
Dense tuple sumOrdered tuples with repetition$O(n)$ direct after sieve$n^k$ grows quicklyAssuming values are distinctState repetition semantics
Quotient groupingSame dense model$O(\sqrt n)$ after prefix dataPowers may overflowWrong block endpointUse the quotient endpoint formula
Linear Möbius sievePrefix of $\mu$$O(V)$ time and spaceMemory proportional to $V$Missing repeated-prime breakTest the whole prefix
Modular outputCounts modulo a targetSame asymptotic workNegative signed termsReturning negative residuesNormalize after accumulation
Sparse large valuesHuge maximum valueDomain-dependentDense memory exhaustionAllocating through the maximumFactor distinct values and map divisors sparsely

Applications, Case Studies and Exercises

Möbius inversion appears whenever common-divisor counts are easy but exact gcd constraints are hard. Typical uses include coprime array pairs, primitive lattice points, visible grid directions, tuples with gcd one, and full gcd histograms.

Practice

Apply Möbius Inversion for Coprime Counting

Case study 1 — count coprime pairs. Use [2, 3, 4, 6]. Compute $C_1=4$, $C_2=3$, $C_3=2$, and the remaining relevant frequencies. Evaluate $\sum_d\mu(d)\binom{C_d}{2}$ and list the two coprime index pairs directly. Success means both methods return 2 and every noncoprime pair cancels.

Case study 2 — process telemetry periods. For millions of positive periods bounded by $10^6$, build frequency and Möbius tables and count unordered coprime device pairs. Duplicates represent distinct devices. Record memory, preprocessing time, maximum intermediate count, and compare small prefixes with a gcd oracle. Success means multiplicities and numeric widths are correct.

Hands-on quotient grouping. For $k=2$ and $k=3$, count ordered tuples in $[1,n]^k$ with gcd one. Compare direct Möbius summation, quotient grouping, and brute force for every $n\le30$. For $n=10^6$, report the block count. Success means exact agreement and explicit order-and-repetition semantics.

Tiered exercises. Recall: state Möbius cancellation. Derive: exchange the pair and divisor sums. Implement: count pairs with gcd exactly $g$. Debug: find the factor-of-two error from using $C_d(C_d-1)$. Extend: compute the full pair-gcd histogram by descending divisor inversion.

Use Möbius inversion when common-divisor counts are easy but exact gcd constraints are hard; use direct gcd enumeration for tiny inputs and as an oracle. Always define ordering, repetition, multiplicity, positivity, maximum-value feasibility, and numeric width before selecting the inner combinatorial count.