<p>There is a tree (i.e.,&nbsp;a connected, undirected graph that has no cycles) consisting of <code>n</code> nodes numbered from <code>0</code> to <code>n - 1</code> and exactly <code>n - 1</code> edges. Each node has a value associated with it, and the <strong>root</strong> of the tree is node <code>0</code>.</p>

<p>To represent this tree, you are given an integer array <code>nums</code> and a 2D array <code>edges</code>. Each <code>nums[i]</code> represents the <code>i<sup>th</sup></code> node&#39;s value, and each <code>edges[j] = [u<sub>j</sub>, v<sub>j</sub>]</code> represents an edge between nodes <code>u<sub>j</sub></code> and <code>v<sub>j</sub></code> in the tree.</p>

<p>Two values <code>x</code> and <code>y</code> are <strong>coprime</strong> if <code>gcd(x, y) == 1</code> where <code>gcd(x, y)</code> is the <strong>greatest common divisor</strong> of <code>x</code> and <code>y</code>.</p>

<p>An ancestor of a node <code>i</code> is any other node on the shortest path from node <code>i</code> to the <strong>root</strong>. A node is <strong>not </strong>considered an ancestor of itself.</p>

<p>Return <em>an array </em><code>ans</code><em> of size </em><code>n</code>, <em>where </em><code>ans[i]</code><em> is the closest ancestor to node </em><code>i</code><em> such that </em><code>nums[i]</code> <em>and </em><code>nums[ans[i]]</code> are <strong>coprime</strong>, or <code>-1</code><em> if there is no such ancestor</em>.</p>

<p>&nbsp;</p>
<p><strong class="example">Example 1:</strong></p>

<p><strong><img alt="" src="https://assets.leetcode.com/uploads/2021/01/06/untitled-diagram.png" style="width: 191px; height: 281px;" /></strong></p>

<pre>
<strong>Input:</strong> nums = [2,3,3,2], edges = [[0,1],[1,2],[1,3]]
<strong>Output:</strong> [-1,0,0,1]
<strong>Explanation:</strong> In the above figure, each node&#39;s value is in parentheses.
- Node 0 has no coprime ancestors.
- Node 1 has only one ancestor, node 0. Their values are coprime (gcd(2,3) == 1).
- Node 2 has two ancestors, nodes 1 and 0. Node 1&#39;s value is not coprime (gcd(3,3) == 3), but node 0&#39;s
  value is (gcd(2,3) == 1), so node 0 is the closest valid ancestor.
- Node 3 has two ancestors, nodes 1 and 0. It is coprime with node 1 (gcd(3,2) == 1), so node 1 is its
  closest valid ancestor.
</pre>

<p><strong class="example">Example 2:</strong></p>

<p><img alt="" src="https://assets.leetcode.com/uploads/2021/01/06/untitled-diagram1.png" style="width: 441px; height: 291px;" /></p>

<pre>
<strong>Input:</strong> nums = [5,6,10,2,3,6,15], edges = [[0,1],[0,2],[1,3],[1,4],[2,5],[2,6]]
<strong>Output:</strong> [-1,0,-1,0,0,0,-1]
</pre>

<p>&nbsp;</p>
<p><strong>Constraints:</strong></p>

<ul>
	<li><code>nums.length == n</code></li>
	<li><code>1 &lt;= nums[i] &lt;= 50</code></li>
	<li><code>1 &lt;= n &lt;= 10<sup>5</sup></code></li>
	<li><code>edges.length == n - 1</code></li>
	<li><code>edges[j].length == 2</code></li>
	<li><code>0 &lt;= u<sub>j</sub>, v<sub>j</sub> &lt; n</code></li>
	<li><code>u<sub>j</sub> != v<sub>j</sub></code></li>
</ul>

from bisect import bisect_left

class Solution:
    def __init__(self):
        self.factorCache = {}
        self.primes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97]

    def getCoprimes(self, nums: List[int], edges: List[List[int]]) -> List[int]:
        l = len(nums)
        self.nums = nums
        edges += list([t[1], t[0]] for t in edges if t[0] != 0)
        edges.sort()
        reachableNums = [[0, []]]
        edgeCount = len(edges)
        self.gcdAncestors = [None] * l
        self.gcdAncestors[0] = -1
        while len(reachableNums) != 0:
            currReachableNums = []
            for reachableNum in reachableNums:
                index = reachableNum[0]
                factors = self.getPrimeFactors(nums[index])
                product = self.getProduct(factors)
                ancestors = reachableNum[1]
                if index != 0:
                    self.gcdAncestors[index] = self.getGcdAncestor(nums[index], ancestors)
                for ancestorIndex in range(0, len(ancestors)):
                    if ancestors[ancestorIndex][0] == product:
                        ancestors.pop(ancestorIndex)
                        break
                ancestors.append([product, index])
                edgeIndex = bisect_left(edges, [index, 0])
                while edgeIndex < edgeCount and edges[edgeIndex][0] == index:
                    newIndex = edges[edgeIndex][1]
                    if self.gcdAncestors[newIndex] == None:
                        currReachableNums.append([newIndex, list(list(v) for v in ancestors)])
                    edgeIndex += 1
            reachableNums = currReachableNums
        return self.gcdAncestors

    def getGcdAncestor(self, num: int, ancestors: List[int]) -> int:
        factors = self.getPrimeFactors(num)
        product = self.getProduct(factors)
        for ancestor in reversed(ancestors):
            if self.isCoprime(num, self.nums[ancestor[1]]):
                return ancestor[1]
        return -1

    def isCoprime(self, x: int, y: int) -> bool:
        if x == 1 or y == 1:
            return True
        if x == y:
            return False
        else:
            factorsX = self.getPrimeFactors(x)
            factorsY = self.getPrimeFactors(y)
            lx = len(factorsX)
            ly = len(factorsY)
            indexX = 0
            indexY = 0
            while indexX < lx and indexY < ly:
                if factorsX[indexX] == factorsY[indexY]:
                    return False
                elif factorsX[indexX] < factorsY[indexY]:
                    indexX += 1
                else:
                    indexY += 1
            return True

    def getPrimeFactors(self, num: int) -> set[int]:
        factors = self.factorCache.get(num)
        if factors != None:
            return factors
        self.factorCache[num] = []
        maxVal = int(num ** 0.5)
        origNum = num
        for prime in self.primes:
            if prime > maxVal:
                break
            if num % prime == 0:
                self.factorCache[origNum].append(prime)
                num //= prime
            while num % prime == 0:
                num //= prime
            if num == 1:
                break
        if num != 1:
            self.factorCache[origNum].append(num)
        return self.factorCache[origNum]

    def getProduct(self, factors: List[int]) -> int:
        if len(factors) == 0:
            return 1
        product = factors[0]
        for factor in factors[1:]:
            product *= factor
        return product


2926