3

I am trying to solve a problem involving printing the product of all divisors of a given number. The number of test cases is a number 1 <= t <= 300000 , and the number itself can range from 1 <= n <= 500000

I wrote the following code, but it always exceeds the time limit of 2 seconds. Are there any ways to speed up the code ?

from math import sqrt

def divisorsProduct(n):
    ProductOfDivisors=1

    for i in range(2,int(round(sqrt(n)))+1):
        if n%i==0: 
            ProductOfDivisors*=i

            if n/i != i:
                ProductOfDivisors*=(n/i)    

    if ProductOfDivisors <= 9999:
        print ProductOfDivisors
    else:
        result = str(ProductOfDivisors)
        print result[len(result)-4:] 

T = int(raw_input())

for i in range(1,T+1):
    num = int(raw_input())
    divisorsProduct(num)

Thank You.

Ben Blank
  • 54,908
  • 28
  • 127
  • 156

3 Answers3

6

You need to clarify by what you mean by "product of divisors." The code posted in the question doesn't work for any definition yet. This sounds like a homework question. If it is, then perhaps your instructor was expecting you to think outside the code to meet the time goals.

If you mean the product of unique prime divisors, e.g., 72 gives 2*3 = 6, then having a list of primes is the way to go. Just run through the list up to the square root of the number, multiplying present primes into the result. There are not that many, so you could even hard code them into your program.

If you mean the product of all the divisors, prime or not, then it is helpful to think of what the divisors are. You can make serious speed gains over the brute force method suggested in the other answers and yours. I suspect this is what your instructor intended.

If the divisors are ordered in a list, then they occur in pairs that multiply to n -- 1 and n, 2 and n/2, etc. -- except for the case where n is a perfect square, where the square root is a divisor that is not paired with any other.

So the result will be n to the power of half the number of divisors, (regardless of whether or not n is a square).

To compute this, find the prime factorization using your list of primes. That is, find the power of 2 that divides n, then the power of 3, etc. To do this, take out all the 2s, then the 3s, etc.

The number you are taking the factors out of will be getting smaller, so you can do the square root test on the smaller intermediate numbers to see if you need to continue up the list of primes. To gain some speed, test p*p <= m, rather than p <= sqrt(m)

Once you have the prime factorization, it is easy to find the number of divisors. For example, suppose the factorization is 2^i * 3^j * 7^k. Then, since each divisor uses the same prime factors, with exponents less than or equal to those in n including the possibility of 0, the number of divisors is (i+1)(j+1)(k+1).

E.g., 72 = 2^3 * 3^2, so the number of divisors is 4*3 = 12, and their product is 72^6 = 139,314,069,504.

By using math, the algorithm can become much better than O(n). But it is hard to estimate your speed gains ahead of time because of the relatively small size of the n in the input.

UncleO
  • 8,299
  • 21
  • 29
  • I was going to add comments but I think you handle all the important math details nicely. See also http://en.wikipedia.org/wiki/Divisor_function – Jason S Jun 04 '09 at 12:43
1

You could eliminate the if statement in the loop by only looping to less than the square root, and check for square root integer-ness outside the loop.

It is a rather strange question you pose. I have a hard time imagine a use for it, other than it possibly being an assignment in a course. My first thought was to pre-compute a list of primes and only test against those, but I assume you are quite deliberately counting non-prime factors? I.e., if the number has factors 2 and 3, you are also counting 6.

If you do use a table of pre-computed primes, you would then have to also subsequently include all possible combinations of primes in your result, which gets more complex.

C is really a great language for that sort of thing, because even suboptimal algorithms run really fast.

Matthias Wandel
  • 6,383
  • 10
  • 33
  • 31
  • 2
    Conversely, C is a really sucky language for this sort of thing, because people can often brute-force without even having to think about coming up with good algorithms. – Johan Kotlinski Jun 02 '09 at 23:53
  • @Matthias: Pre-computing primes and only testing them is reasonable. Once he has a prime factorization of his number, computing the non-prime divisors is simple. – Brian Jun 03 '09 at 00:45
1

Okay, I think this is close to the optimal algorithm. It produces the product_of_divisors for each number in range(500000).

import math

def number_of_divisors(maxval=500001):
    """ Example: the number of divisors of 12 is 6:  1, 2, 3, 4, 6, 12.
        Given a prime factoring of n, the number of divisors of n is the
        product of each factor's multiplicity plus one (mpo in my variables).

        This function works like the Sieve of Eratosthenes, but marks each
        composite n with the multiplicity (plus one) of each prime factor. """
    numdivs = [1] * maxval   # multiplicative identity
    currmpo = [0] * maxval

    # standard logic for 2 < p < sqrt(maxval)
    for p in range(2, int(math.sqrt(maxval))):
        if numdivs[p] == 1:   # if p is prime
            for exp in range(2,50): # assume maxval < 2^50
                pexp = p ** exp
                if pexp > maxval:
                    break
                exppo = exp + 1
                for comp in range(pexp, maxval, pexp):
                    currmpo[comp] = exppo
            for comp in range(p, maxval, p):
                thismpo = currmpo[comp] or 2
                numdivs[comp] *= thismpo
                currmpo[comp] = 0  # reset currmpo array in place

    # abbreviated logic for p > sqrt(maxval)
    for p in range(int(math.sqrt(maxval)), maxval):
        if numdivs[p] == 1:   # if p is prime
            for comp in range(p, maxval, p):
                numdivs[comp] *= 2

    return numdivs

# this initialization times at 7s on my machine
NUMDIV = number_of_divisors()

def product_of_divisors(n):
    if NUMDIV[n] % 2 == 0:
        # each pair of divisors has product equal to n, for example
        # 1*12 * 2*6 * 3*4  =  12**3
        return n ** (NUMDIV[n] / 2)
    else:
        # perfect squares have their square root as an unmatched divisor
        return n ** (NUMDIV[n] / 2) * int(math.sqrt(n))

# this loop times at 13s on my machine
for n in range(500000):
    a = product_of_divisors(n)

On my very slow machine, it takes 7s to compute the numberofdivisors for each number, then 13s to compute the productofdivisors for each. Of course it can be sped up by translating it into C. (@someone with a fast machine: how long does it take on your machine?)

krubo
  • 5,969
  • 4
  • 37
  • 46
  • My machine runs your code in 2.565s of real time (2.479s of user time). I haven't tried timing the sections separately. – Will Harris Jun 04 '09 at 20:24