How do I count from a given range a number that is not divisible by any element of the array?

Question:

In between 1 and 1000000000000000000, how many numbers other than multiples of the given numbers can be found fast and with memory usage?

For example, how many numbers between 1 and 1000000000000000000 are not multiples of [29516, 15, 63315, 94, 43, 77, 33422, 84834, 43803, 61310]?
It would be helpful if you could give me the code in python.

# Python3 program for the above approach
 
# Function to find the non-multiples
# till k
def findNonMultiples(arr, n, k):
 
    # Stores all unique multiples
    multiples = set([])
 
    # Iterate the array
    for i in range(n):
 
        # For finding duplicates
        # only once
        if (arr[i] not in multiples):
 
            # Inserting all multiples
            # into the set
            for j in range(1, k // arr[i] + 1):
                multiples.add(arr[i] * j)
 
    # Returning only the count of
    # numbers that are not divisible
    # by any of the array elements
    return k - len(multiples)
 
# Function to count the total values
# in the range [L, R]
def countValues(arr, N, L, R):
 
    # Count all values in the range
    # using exclusion principle
    return (findNonMultiples(arr, N, R) -
            findNonMultiples(arr, N, L - 1))
 
# Driver Code
if __name__ == "__main__":
   
    arr = [ 29516,15,63315,94,43,77,33422,84834,43803,61310 ]
    N = len(arr)
    L = 1
    R = 1000000000000000000
     
    # Function Call
    print( countValues(arr, N, L, R))
 
# This code is contributed by chitranayal

This code is too slow and uses a lot of memory. How can I improve it?

Asked By: panda

||

Answers:

Here is a mathematical method of conjecture that I have not tried to prove. I do not guarantee that the results obtained are completely correct.

My idea is to select values from the given list one by one to remove from the range, and calculate the number of numbers to be removed each time. According to the example provided by OP, first remove the multiples of 29516 from 10 ** 18, and the number of removal is:

>>> hi = 10 ** 18
>>> hi // 29516
33879929529746

Remove 15 for the second time. Considering that the minimum common multiple of 15 and 29516 is 442740, we need to remove the following number:

>>> hi // 15 - hi // 442740
66664408004698017

Remove 65 for the third time, and the number to be removed is:

>>> hi // 63315 - hi // lcm(29516, 63315) - hi // lcm(15, 63315) + hi // lcm(29516, 15, 63315)
0

Here lcm is used to find the least common multiple.

This concludes the example. Here’s my guess: suppose that the number list that has been removed is nums, the number to be removed currently is num. The number of numbers to be removed is expressed in Python code as:

hi // num
- sum(hi // lcm(*comb, num) for comb in combinations(nums, 1))
+ sum(hi // lcm(*comb, num) for comb in combinations(nums, 2))
- ...
+/- sum(hi // lcm(*comb, num) for comb in combinations(nums, len(nums)))

The following is the implementation code:

from math import lcm
from itertools import combinations


def num_removed(num, nums, hi):
    for i in range(0, len(nums) + 1, 2):
        yield sum(hi // lcm(*comb, num) for comb in combinations(nums, i))
    for i in range(1, len(nums) + 1, 2):
        yield -sum(hi // lcm(*comb, num) for comb in combinations(nums, i))


def main(nums, hi):
    return hi - sum(sum(num_removed(num, nums[:i], hi)) for i, num in enumerate(nums))

Calculation results:

>>> main([29516, 15, 63315, 94, 43, 77, 33422, 84834, 43803, 61310], 10 ** 18)
890153441245772172

The above conjecture is based on the special case that the lower bound is 1. If the lower bound is greater than 1, a simple implementation method is to calculate the number of numbers to be removed twice, and then calculate their difference:

def num_is_multiples(nums, hi):
    return sum(sum(num_removed(num, nums[:i], hi)) for i, num in enumerate(nums))


def num_non_multiples(nums, lo, hi):
    return hi - lo + 1 - (num_is_multiples(nums, hi) - num_is_multiples(nums, lo - 1))

Calculation results:

>>> num_non_multiples([29516, 15, 63315, 94, 43, 77, 33422, 84834, 43803, 61310], 200, 10 ** 18)
890153441245771994
Answered By: Mechanic Pig
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.