LMU ☀️ CMSI 2130
ALGORITHMS
Midterm Answers
  1. By inspection, $\Theta(\sqrt{n} \log n)$
  2. Note that $f(n) = \sqrt{n^3} = n^{1.5}$ and $g(n) = 5^{\log_3{n}} = n^{\log_3{5}} \approx n^{1.465}$. The former is the higher degree polynomial, so $f \in \Omega(g)$.
  3. Array: [19,17,4,1,5,3]. Tree:
         19   
        /  \  
      17    4 
     / \   /  
    1   5 3   
    
  4. The tail recursive version is
    def c(n, a=0):
        return a if n==1 else c(n/2 if n%2==0 else 3*n+1, a+1)
    
  5. Our computer does $2^{30}$ operations in 256 seconds, so the speed is $2^{22} ops/sec$.
    1. $\frac{2^{40} ops}{2^{22} ops/sec} = 2^{18} sec \approx $ 3.03 days.
    2. $\frac{2^{50} ops}{2^{22} ops/sec} = 2^{28} sec \approx $ 8.51 years.
    3. New computer’s speed is $10^9\cdot 2^{22} ops/sec.$ In one day, $\lfloor\log_2 (10^9 \cdot 2^{22} \cdot 86400)\rfloor = $ 68 elements.
    4. $\frac{2^{80} ops}{604800\:sec} \div 2^{22} ops/sec = \frac{2^{58}}{604800} = 476,571,389,140 \approx $ 477 billion times faster.
  6. Our computer does $8 \log_2{512} = 72$ operations in 144 seconds, so the speed is $0.5\:ops/sec$.
    1. $\frac{8 \log_2(2048)\:ops}{0.5\:ops/sec} = $ 176 sec.
    2. $\frac{8 \log_2(1048576)\:ops}{0.5\:ops/sec} = $ 320 sec.
    3. New computer’s speed is $500\:ops/sec$. In one day, we can do 43200000 operations. We have to find $x$ such that $8 \log_2 x = 43200000$. So $\log_2 x = 5400000$, meaning we can do $2^{5400000}$ elements.
    4. $\frac{8 \log_2(4096)\:ops}{60\:sec} \div 0.5\:ops/sec = $ 3.2 times faster.
  7. The obvious but not-so-good way to write this is:
    def is_prime(n):
        return math.factorial(n-1) % n == n-1
    
    But this is not good enough for full credit because the size of the number $(n-1)!$ is massive (exponential in the number of bits) and by computing the entire factorial first, the gigantic number has to be stored, and there might not be enough space. The factorial of 1023, a 10-bit number, contains 8,762 bits, so how the hell are you going to store the factorial of a 1000 bit number? (Do the math!) You should have learned, when we studied modular arithmetic, that we can do the modulo after each multiplication. For example, $$ (5 \cdot 4 \cdot 3 \cdot 2 \cdot 1) \bmod 6 $$ is equal to $$ (((5 \cdot 4 \bmod 6) \cdot 3 \bmod 6) \cdot 2 \bmod 6) \cdot 1 \bmod 6 $$ So if you want full credit, then would avoid generating the massive factorial, and you should modulo at each step:
    def is_prime(n):
        p = 1
        for i in range(2,n):
            p = (p * i) % n
        return p == (n-1)
    
    However, why bother? Both of these are completely useless in practice, because computing $(n − 1)! \;\textrm{mod}\; n$ is slow, ridiculously slow. It’s way slower than even doing trial division, which we know is already terrible. In fact, to see how horrific things are find the best known complexity for modular factorial at the Wikipedia complexity page. The intuition is that when doing modular factorials, we are doing about as many multiplications as the magnitude of the number; we don’t know how to divide-and-conquer! So this approach is utterly useless. No one uses it.
  8. Given $p=876872342387$, $q=276872342389$, $e=5$, we compute $N = pq = 242781699412817901542543$ and $\phi = 242781699411664156857768$ and $d = \mathtt{invmod}(e,\phi) = 145669019646998494114661$. Therefore
    • PublicKey $= (N,e) =$ $(242781699412817901542543,5)$
    • PrivateKey $= (N,d) =$ $(242781699412817901542543,145669019646998494114661)$
  9. The number of keys is equal to the number of ways we can arrange the values 1 through 16 inclusive, which is 16 factorial, or 20,922,789,888,000, which is a little shy of 21 trillion.
  10. 4, because $3^4 \equiv 13 \pmod {17}$.