Nima Aghdaii

I’ve worked on ads at Meta, Snap, and Nextdoor. I also consult companies on building ads. If you are building your ad stack, feel free to reach out — let’s chat!

C(n, k) mod 2

23 Oct 2020 » math, bits, fractal, algorithm, problems

Problem

Given n and k calculate c(n, k) % 2.


O(n * k)

We can just use DP:

maxn = 1000
c = [[0] * maxn for i in range(maxn)]
for i in range(maxn):
    c[i][i] = 1
    for j in range(i):
        c[i][j] = (c[i-1][j] + c[i-1][j-1]) % 2


O(n)

Since we only want the answer mod 2. we just need to find how many 2s there are in the numerator vs. denominator of n! / (k! (n-k)!). To do that we just need to find the number of 2s in x!.

# count 2s in n!
def twos(n):
    res = 0
    t = [0] * (n+1)
    for i in range(2, n+1, 2):
        t[i] = 1 + t[i // 2]
        res += t[i]
    return res

def c(n, k):
    num = twos(n)
    den = twos(n-k) + twos(k)
    return 0 if num > den else 1  


O(log(n))

To improve the linear solution, let’s look at twos function. instead of iterating over each number in n! (1, 2, 3, …, n) let’s count how many 2s we have, then how many 4s we have, then 8s and so on (until log2(n)). This leads to a O(log(n)) solution:

# count 2s in n!
def twos(n):
    res = 0
    p = 1
    for i in range(1, n):
        if p > n:
            break
        p *= 2
        res += n // p
    return res

def c(n, k):
    num = twos(n)
    den = twos(n-k) + twos(k)
    return 0 if num > den else 1  


O(1)

Now, can we do it faster? Let’s first print out c(n, k) % 2 and look at it:

maxn = 32
for i in range(maxn):
    for j in range(i+1):
        print(c(i, j), end=' ')
    print()

Output:

triangle

Yes, this is the famous Sierpiński triangle fractal!

As you probably know, this simple code prints the Sierpiński triangle but upside down:

N = 32
for i in range(N):
    for j in range(N):
        print('0' if i & j else '1', end=' ')
    print()

flip-tri

Now, changing i & j to (i - j) & j would flip the triangle and we get:

right-tri

This perfectly matches the triangle we got from c(n, k) % 2, which suggests the simple O(1) solution:

# c(n, k) mod 2
def c(n, k):
    return 0 if (n - k) & k else 1


Proof

Let’s take a closer look and actually prove why this simple solution works. To solve c(n, k) % 2 Let’s use Lucas’s theorem. It states that we can compute c(n, k) by breaking down n and k into their digits in base p (p needs to be prime):

lucas

Substituting for p = 2, each digit is either 0 or 1. So:

c(0, 0) = c(1, 0) = c(1, 1) = 1

while,

c(0, 1) = 0

Now, based on Lucas’s theorem, in order for c(n, k) % 2 to be 1 all the terms on the right hand side need to be 1. This means for any set bit in k the corresponding bit in n should be set as well. In other words:

n | k == n

or

n & k == k

or

(n - k) & k == 0