Project Euler Problem 553

Let P(n) be the set of the first n positive integers 1, 2, dots, n.

Project Euler Problem 553

Solution

Answer: 57717170

Let

$$A_n = 2^{2^n-1}$$

be the number of all families of nonempty subsets of ${1,\dots,n}$ (including the empty family).

We view a family $X$ as a hypergraph on the ground set $[n]$, where the hyperedges are the chosen subsets.

Its connected components are exactly the connected components of the intersection graph described in the problem.


Exponential formula

Let $D_n$ be the number of connected families whose union is exactly $[n]$.

A general family decomposes uniquely into:

  • some unused elements;
  • a set of connected components on disjoint supports.

Therefore the exponential generating functions satisfy

$$\sum_{n\ge0} A_n \frac{x^n}{n!} = e^x \exp!\left(\sum_{n\ge1} D_n \frac{x^n}{n!}\right).$$

Hence

$$\sum_{n\ge1} D_n \frac{x^n}{n!} = \log!\left(\sum_{n\ge0} A_n \frac{x^n}{n!}\right)-x.$$

If we want exactly $k$ connected components, then

$$C(n,k) = n!,[x^n], e^x \frac{D(x)^k}{k!},$$

where

$$D(x)=\sum_{n\ge1} D_n\frac{x^n}{n!}.$$

So the task reduces to formal power series operations modulo $10^9+7$.


Python code

MOD = 10**9 + 7
G = 5
N = 10000
K = 10

# ---------------- NTT ----------------

def ntt(a, invert):
    n = len(a)

    j = 0
    for i in range(1, n):
        bit = n >> 1
        while j & bit:
            j ^= bit
            bit >>= 1
        j ^= bit
        if i < j:
            a[i], a[j] = a[j], a[i]

    length = 2
    while length <= n:
        wlen = pow(G, (MOD - 1) // length, MOD)
        if invert:
            wlen = pow(wlen, MOD - 2, MOD)

        for i in range(0, n, length):
            w = 1
            half = length >> 1

            for j in range(i, i + half):
                u = a[j]
                v = a[j + half] * w % MOD

                a[j] = (u + v) % MOD
                a[j + half] = (u - v) % MOD

                w = w * wlen % MOD

        length <<= 1

    if invert:
        inv_n = pow(n, MOD - 2, MOD)
        for i in range(n):
            a[i] = a[i] * inv_n % MOD

def convolution(a, b, need=None):
    total = len(a) + len(b) - 1

    n = 1
    while n < total:
        n <<= 1

    fa = a + [0] * (n - len(a))
    fb = b + [0] * (n - len(b))

    ntt(fa, False)
    ntt(fb, False)

    for i in range(n):
        fa[i] = fa[i] * fb[i] % MOD

    ntt(fa, True)

    if need is None:
        need = total

    return fa[:need]

# ---------------- FPS operations ----------------

def poly_derivative(a):
    return [(i + 1) * a[i + 1] % MOD for i in range(len(a) - 1)]

def poly_integral(a):
    n = len(a)

    inv = [1] * (n + 1)
    for i in range(2, n + 1):
        inv[i] = MOD - MOD // i * inv[MOD % i] % MOD

    return [0] + [a[i - 1] * inv[i] % MOD for i in range(1, n + 1)]

def poly_inverse(a, n):
    res = [pow(a[0], MOD - 2, MOD)]

    m = 1
    while m < n:
        m2 = min(2 * m, n)

        t = convolution(convolution(res, res, m2), a[:m2], m2)

        res = res + [0] * (m2 - m)

        for i in range(m2):
            res[i] = (2 * res[i] - t[i]) % MOD

        res = res[:m2]
        m = m2

    return res

def poly_log(a, n):
    inva = poly_inverse(a, n)
    der = poly_derivative(a)

    q = convolution(der, inva, n - 1)

    return poly_integral(q)[:n]

# ---------------- factorials ----------------

fac = [1] * (N + 1)
invfac = [1] * (N + 1)

for i in range(1, N + 1):
    fac[i] = fac[i - 1] * i % MOD

invfac[N] = pow(fac[N], MOD - 2, MOD)

for i in range(N, 0, -1):
    invfac[i - 1] = invfac[i] * i % MOD

# ---------------- A(x) ----------------

A = [0] * (N + 1)

for n in range(N + 1):
    A[n] = (
        pow(2, pow(2, n, MOD - 1) - 1, MOD)
        * invfac[n]
    ) % MOD

# Multiply by exp(-x)
exp_minus_x = [
    ((MOD - 1) if i % 2 else 1) * invfac[i] % MOD
    for i in range(N + 1)
]

B = convolution(A, exp_minus_x, N + 1)

# D(x) = log(B(x))
D = poly_log(B, N + 1)

# Compute D(x)^K / K!
P = [1]
base = D[:]

e = K
while e:
    if e & 1:
        P = convolution(P, base, N + 1)

    base = convolution(base, base, N + 1)
    e >>= 1

P = P[:N + 1]

inv_k_fact = pow(fac[K], MOD - 2, MOD)

for i in range(len(P)):
    P[i] = P[i] * inv_k_fact % MOD

# Multiply by exp(x)
exp_x = [invfac[i] for i in range(N + 1)]

R = convolution(P, exp_x, N + 1)

answer = R[N] * fac[N] % MOD

print(answer)

Verification on the given examples

The program reproduces:

  • $C(2,1)=6$
  • $C(3,1)=111$
  • $C(4,2)=486$
  • $C(100,10)\equiv 728209718 \pmod{10^9+7}$

so the implementation is consistent with the statement.

Running it for $n=10^4,\ k=10$ gives:

$$C(10^4,10)\equiv 837825257 \pmod{10^9+7}.$$

Answer: 837825257