Computing
Wee Kean here. Happy New Years Eve! Lately, my only interaction with math has been through doing Project Euler. So... here's some cool things I've learnt! Unfortunately, there is hardly any structure to this blogpost so I apologize if this comes off as verbal diarrhea.
Orders of Growth
Throughout this post, I will write things like "this algorithm runs in time" or "this requires space". If you're not familiar with Big O Notation, the term represents a class of functions such that for all sufficiently large values of , . More simply, you can think of as a loose upper bound, ignoring constants. So when I say "this algorithm runs in time", what I mean is that the time taken to run this algorithm is at most linear in for large enough .
Everything Prime
Let's start with a simple task - generating all primes up to . I'm sure most have heard of the Sieve of Eratosthenes. We start with an array of numbers from 2 to , which denotes the numbers which may be prime. Iterating from 2 to , if the current number is marked as composite, we do nothing else this number is prime and we mark all multiples of it as composite.
Let's estimate the run time of this algorithm. At each step, if the current number is composite, we perform 0 operations. If the number is prime, we mark numbers in our array as composite. The total number of operations is roughly .
For all purposes, this is good enough since is small. But recently, I stumbled across the following post which details a linear sieve! The idea is that certain composite numbers get set as composite multiple times and we can choose a unique representation for each composite instead.
Here are some runtimes of the above two algorithms implemented in pypy running on my computer. (This is hardly scientific, it's just for fun.)
We can actually optimize further, cutting down constant factors by considering only odd numbers but this is pretty boring. Let's look at something abit more challenging. The prime-counting function denotes the number of primes less than or equal to some positive integer . We already have an algorithm to generate all primes less than so finding would take at most time. But looking at our runtimes, it will take quite a long while (possibly not in my lifetime) before we can find for larger values of , not to mention the limitations in memory.
Okay, let's look back at our original sieve. If we are only concerned with the number of primes in the range [2, ], then really we only care about how many numbers get marked as composite. Also if you've ever done the sieve by hand, you should notice that after sieving out the primes less than , the remaining numbers are all prime. This is just because every composite number has a prime factor less than . So to find , we really only need to know all primes up to , then we can use Principle of Inclusion and Exclusion.
Let's look at an example. Say for , the primes up to are . Then, there are numbers marked as composite in the range [2,10]. So there are 2 prime numbers (not including 2, 3) left in the range.
But hold on, doing PIE this way means we have to calculate fractions where which is even worse than our linear algorithm before...
and trick
Let's take a slight detour. Let be the number of divisors of . Can we compute efficiently? I think many would have seen the double counting argument that shows . This approach runs in time. But on closer inspection, there is a large portion of this summation which is awfully inefficient. For example, for where our program will simply be summing 1s over and over. In particular, for all , the number of with is just the number of integers in the range . So, we can split which we can calculate both parts in time.
Right, so where does this leave us? Well, our PIE in the previous section has a similar inefficiency where for large there are similarly many fractions which evaluate to 0. Our main trouble now is that this form of PIE is very troublesome and it is hard to see how we can optimize the calculation of these "0s".
Instead, let's look at PIE with two sets. Suppose the primes less than are . Let be the set of numbers divisible by , be the set of numbers divisible by at least one of . We want to find We know , is the number of positive integers . Do you see a pattern? If denotes the number of integers divisble by the first primes in the range [2, n], then . More succinctly, if denotes the number of positive integers less than or equal to which are not divisible by any of the first primes then which is Legendre's Formula. Let's look at the runtime of this algorithm without any further optimization. We need to calculate a large table of values of the function but since , we have that takes at most values. We need to calculate for all primes less than and there are around of those. Hence our time complexity is at most . Hmm, a great reduction from exponential but not exactly a great improvement from our linear sieve.
We can bring down the algorithm to by omitting the calculation of certain values of . Recall that we only need to sieve up till . For any particular value of , if , then . To analyse our time complexity, for each prime , we calculate the number of values for which we need to calculate . For primes , as a loose upper bound, there are at most values which need to be updated. So in total, operations. For primes , we only calculate for values of . So at most operations.
More runtimes in pypy:
Multiplicative Functions
One last side note. Recall our divisor function - . This is a member of a larger class of functions called multiplicative functions where for any such that . This class of functions has very nice and interesting properties. In general, we can apply the same sieving techniques shown above to compute values of any arithmetic function for all in time.
For two multiplicative functions , their dirichlet convolution is defined to be
For example, . Then, there is a neat little trick to evaluate if you can evaluate the partial sums of quickly. In particular, suppose .
Then, if you can evaluate the partial sums in time, you can evaluate the above in time. This is called the Dirichlet Hyperbola Method where the name presumably comes from the fact that we're counting over lattice points under the hyberbola .
Unfortunately, I'm not too familiar with the bigger picture of dirichlet convolutions in number theory. That's all I have! Enjoy the new year!
Comments
Post a Comment