Ebad
Ebad

Reputation: 51

Count the number of possible ways to solve an equation

This question was asked in a challenge in HackerEarth:

Mark is solving an interesting question. He needs to find out number of distinct ways such that
(i + 2*j+ k) % (x + y + 2*z) = 0, where 1 <= i,j,k,x,y,z <= N

Help him find it.

Constraints:

1<= T<= 10

1<=N<= 1000

Input Format:

First line contains T, the number of test cases. Each of the test case contains a single integer,N in a separate line.

Output Format:

For each test case , output in a separate line, the number of distinct ways.

Sample Input
2

1

2

Sample Output

1

15

Explanation

In the first case, the only possible way is i = j = k = x =y = z = 1

I am not getting any way how to solve this problem, I have tried one and I know it's not even close to the question.

import random

def CountWays (N):

    # Write your code here
    i = random.uniform(1,N)
    j = random.uniform(1,N)
    k = random.uniform(1,N)
    x = random.uniform(1,N)
    y = random.uniform(1,N)
    z = random.uniform(1,N)
    d = 0
    for i in range(N):
        if (i+2*j+k)%(x+y+2*z)==0:
            d += 1
    return d

T = int(input())

for _ in range(T):

    N = int(input())

    out_ = CountWays(N)
    print (out_)

My Output

0

0

Instead it should give the output

1

15

Upvotes: 0

Views: 604

Answers (3)

user2653663
user2653663

Reputation: 2948

The value of the numerator (num) can range from 4 to 4N. The value of the denominator (dom) can range from 4 to num. You can split your problem into two smaller problems: 1) How many values of the denominator is a given value of the numerator divisible by? 2) How many ways can a given denominator and numerator be constructed?

To answer 1) we can simply loop through all the possible values of the numerator, then loop over all the values of the denominator where numerator % denominator == 0. To answer 2) we can find all the partitions of the numerator and denominator that satisfies the equality and constraints. The number of ways to construct a given numerator and denominator will be the product of the number of partitions of each.

import itertools

def divisible_numbers(n):
    """
    Get all numbers with which n is divisible.
    """
    for i in range(1,n+1):
        if n % i == 0:
            yield i
        if i >= n:
            break

def get_partitions(n):
    """
    Generate ALL ways n can be partitioned into 3 integers.
    Modified from http://code.activestate.com/recipes/218332-generator-for-integer-partitions/#c9
    """

    a = [1]*n
    y = -1
    v = n
    while v > 0:
        v -= 1
        x = a[v] + 1
        while y >= 2 * x:
            a[v] = x
            y -= x
            v += 1
        w = v + 1
        while x <= y:
            a[v] = x
            a[w] = y
            if w == 2:
                yield a[:w + 1]
            x += 1
            y -= 1
        a[v] = x + y
        y = a[v] - 1
        if w == 3:
            yield a[:w]

def get_number_of_valid_partitions(num, N):
    """
    Get number of valid partitions of num, given that
    num = i + j + 2k, and that 1<=i,j,k<=N
    """
    n = 0
    for partition in get_partitions(num):
        # This can be done a bit more cleverly, but makes
        # the code extremely complicated to read, so
        # instead we just brute force the 6 combinations,
        # ignoring non-unique permutations using a set
        for i,j,k in set(itertools.permutations(partition)):
            if i <= N and j <= N and k <= 2*N and k % 2 == 0:
                n += 1
    return n

def get_number_of_combinations(N):
    """
    Get number of ways the equality can be solved under the given constraints
    """
    out = 0
    # Create a dictionary of number of valid partitions
    # for all numerator values we can encounter
    n_valid_partitions = {i: get_number_of_valid_partitions(i, N) for i in range(1,4*N+1)}

    for numerator in range(4,4*N+1):
        numerator_permutations = n_valid_partitions[numerator]
        for denominator in divisible_numbers(numerator):
            denominator_permutations = n_valid_partitions[denominator]
            if denominator < 4:
                continue
            out += numerator_permutations * denominator_permutations
    return out

N = 2
out = get_number_of_combinations(N)
print(out)

The scaling of the code right now is very poor due to the way the get_partitions and the get_number_of_valid_partitions functions interact.

EDIT

The following code is much faster. There's a small improvement to divisible_numbers, but the main speedup lies in get_number_of_valid_partitions not creating a needless amount of temporary lists as it has now been joined with get_partitions in a single function. Other big speedups comes from using numba. The code of get_number_of_valid_partitions is all but unreadable now, so I've added a much simpler but slightly slower version named get_number_of_valid_partitions_simple so you can understand what is going on in the complicated function.

import numba

@numba.njit
def divisible_numbers(n):
    """
    Get all numbers with which n is divisible.
    Modified from·
    """
    # We can save some time by only looking at
    # values up to n/2
    for i in range(4,n//2+1):
        if n % i == 0:
            yield i
    yield n

def get_number_of_combinations(N):
    """
    Get number of ways the equality can be solved under the given constraints
    """
    out = 0
    # Create a dictionary of number of valid partitions
    # for all numerator values we can encounter
    n_valid_partitions = {i: get_number_of_valid_partitions(i, N) for i in range(4,4*N+1)}

    for numerator in range(4,4*N+1):
        numerator_permutations = n_valid_partitions[numerator]
        for denominator in divisible_numbers(numerator):
            if denominator < 4:
                continue
            denominator_permutations = n_valid_partitions[denominator]
            out += numerator_permutations * denominator_permutations
    return out

@numba.njit
def get_number_of_valid_partitions(num, N):
    """
    Get number of valid partitions of num, given that
    num = i + j + 2l, and that 1<=i,j,l<=N.
    """
    count = 0
    # In the following, k = 2*l
    #There's different cases for i,j,k that we can treat separately
    # to give some speedup due to symmetry.
    #i,j can be even or odd. k <= N or N < k <= 2N.

    # Some combinations only possible if num is even/odd
    # num is even
    if num % 2 == 0:
        # i,j odd, k <= 2N
        k_min = max(2, num - 2 * (N - (N + 1) % 2))
        k_max = min(2 * N, num - 2)
        for k in range(k_min, k_max + 1, 2):
            # only look at i<=j
            i_min = max(1, num - k - N + (N + 1) % 2)
            i_max = min(N, (num - k)//2)
            for i in range(i_min, i_max + 1, 2):
                j = num - i - k
                # if i == j, only one permutations
                # otherwise two due to symmetry
                if i == j:
                    count += 1
                else:
                    count += 2

        # i,j even, k <= N
        # only look at k<=i<=j
        k_min = max(2, num - 2 * (N - N % 2))
        k_max = min(N, num // 3)
        for k in range(k_min, k_max + 1, 2):
            i_min = max(k, num - k - N + N % 2)
            i_max = min(N, (num - k) // 2)
            for i in range(i_min, i_max + 1, 2):
                j = num - i - k
                if i == j == k:
                    # if i == j == k, only one permutation
                    count += 1
                elif i == j or i == k or j == k:
                    # if only two of i,j,k are the same there are 3 permutations
                    count += 3
                else:
                    # if all differ, there are six permutations
                    count += 6

        # i,j even, N < k <= 2N
        k_min = max(N + 1 + (N + 1) % 2, num - 2 * N)
        k_max = min(2 * N, num - 4)
        for k in range(k_min, k_max + 1, 2):
            # only look for i<=j
            i_min = max(2, num - k - N + 1 - (N + 1) % 2)
            i_max = min(N, (num - k) // 2)
            for i in range(i_min, i_max + 1, 2):
                j = num - i - k
                if i == j:
                    # if i == j, only one permutation
                    count += 1
                else:
                    # if all differ, there are two permutations
                    count += 2
    # num is odd
    else:
        # one of i,j is even, the other is odd. k <= N
        # We assume that j is odd, k<=i and correct the symmetry in the counts
        k_min = max(2, num - 2 * N + 1)
        k_max = min(N, (num - 1) // 2)
        for k in range(k_min, k_max + 1, 2):
            i_min = max(k, num - k - N + 1 - N % 2)
            i_max = min(N, num - k - 1)
            for i in range(i_min, i_max + 1, 2):
                j = num - i - k
                if i == k:
                    # if i == j, two permutations
                    count += 2
                else:
                    # if i and k differ, there are four permutations
                    count += 4

        # one of i,j is even, the other is odd. N < k <= 2N
        # We assume that j is odd and correct the symmetry in the counts
        k_min = max(N + 1 + (N + 1) % 2, num - 2 * N + 1)
        k_max = min(2 * N, num - 3)
        for k in range(k_min, k_max + 1, 2):
            i_min = max(2, num - k - N + (N + 1) % 2)
            i_max = min(N, num - k - 1)
            for i in range(i_min, i_max + 1, 2):
                j = num - i - k
                count += 2

    return count

@numba.njit
def get_number_of_valid_partitions_simple(num, N):
    """
    Simpler but slower version of 'get_number_of_valid_partitions'
    """
    count = 0

    for k in range(2, 2 * N + 1, 2):
        for i in range(1, N + 1):
            j = num - i - k
            if 1 <= j <= N:
                count += 1
    return count

if __name__ == "__main__":
    N = int(sys.argv[1])
    out = get_number_of_combinations(N)
    print(out)

Upvotes: 1

גלעד ברקן
גלעד ברקן

Reputation: 23955

(i + 2*j+ k) % (x + y + 2*z) = 0, where 1 <= i,j,k,x,y,z <= N

(2*j + i + k) is a multiple of (2*z + x + y)

N = 2

min(2*j + i + k) = 4
max(2*j + i + k) = 8

ways to make 4: 1 * 1 = 1
ways to make 5: 2 * 2 = 4
ways to make 6: 2 * 2 = 4
ways to make 7: 2 * 2 = 4
ways to make 8: 1 * 1 = 1

Total = 14

But 8 is a multiple of 4 so we add one more instance for a total of 15.

Upvotes: 0

OneCricketeer
OneCricketeer

Reputation: 191983

The current issue with your code is that you've picked random numbers once, then calculate the same equation N times.

I assume you wanted to generate 1..N for each individual variable, which would require 6 nested loops from 1..N, for each variable

Now, that's the brute force solution, which probably fails on large N values, so as I commented, there's some trick to find the multiples of the right side of the modulo, then check if the left side portion is contained in that list. That would only require two triple nested lists, I think

Upvotes: 0

Related Questions