<p>You are given a <strong>0-indexed</strong> 2D integer array <code>tires</code> where <code>tires[i] = [f<sub>i</sub>, r<sub>i</sub>]</code> indicates that the <code>i<sup>th</sup></code> tire can finish its <code>x<sup>th</sup></code> successive lap in <code>f<sub>i</sub> * r<sub>i</sub><sup>(x-1)</sup></code> seconds.</p>

<ul>
	<li>For example, if <code>f<sub>i</sub> = 3</code> and <code>r<sub>i</sub> = 2</code>, then the tire would finish its <code>1<sup>st</sup></code> lap in <code>3</code> seconds, its <code>2<sup>nd</sup></code> lap in <code>3 * 2 = 6</code> seconds, its <code>3<sup>rd</sup></code> lap in <code>3 * 2<sup>2</sup> = 12</code> seconds, etc.</li>
</ul>

<p>You are also given an integer <code>changeTime</code> and an integer <code>numLaps</code>.</p>

<p>The race consists of <code>numLaps</code> laps and you may start the race with <strong>any</strong> tire. You have an <strong>unlimited</strong> supply of each tire and after every lap, you may <strong>change</strong> to any given tire (including the current tire type) if you wait <code>changeTime</code> seconds.</p>

<p>Return<em> the <strong>minimum</strong> time to finish the race.</em></p>

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

<pre>
<strong>Input:</strong> tires = [[2,3],[3,4]], changeTime = 5, numLaps = 4
<strong>Output:</strong> 21
<strong>Explanation:</strong> 
Lap 1: Start with tire 0 and finish the lap in 2 seconds.
Lap 2: Continue with tire 0 and finish the lap in 2 * 3 = 6 seconds.
Lap 3: Change tires to a new tire 0 for 5 seconds and then finish the lap in another 2 seconds.
Lap 4: Continue with tire 0 and finish the lap in 2 * 3 = 6 seconds.
Total time = 2 + 6 + 5 + 2 + 6 = 21 seconds.
The minimum time to complete the race is 21 seconds.
</pre>

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

<pre>
<strong>Input:</strong> tires = [[1,10],[2,2],[3,4]], changeTime = 6, numLaps = 5
<strong>Output:</strong> 25
<strong>Explanation:</strong> 
Lap 1: Start with tire 1 and finish the lap in 2 seconds.
Lap 2: Continue with tire 1 and finish the lap in 2 * 2 = 4 seconds.
Lap 3: Change tires to a new tire 1 for 6 seconds and then finish the lap in another 2 seconds.
Lap 4: Continue with tire 1 and finish the lap in 2 * 2 = 4 seconds.
Lap 5: Change tires to tire 0 for 6 seconds then finish the lap in another 1 second.
Total time = 2 + 4 + 6 + 2 + 4 + 6 + 1 = 25 seconds.
The minimum time to complete the race is 25 seconds. 
</pre>

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

<ul>
	<li><code>1 &lt;= tires.length &lt;= 10<sup>5</sup></code></li>
	<li><code>tires[i].length == 2</code></li>
	<li><code>1 &lt;= f<sub>i</sub>, changeTime &lt;= 10<sup>5</sup></code></li>
	<li><code>2 &lt;= r<sub>i</sub> &lt;= 10<sup>5</sup></code></li>
	<li><code>1 &lt;= numLaps &lt;= 1000</code></li>
</ul>

class Solution:
    def minimumFinishTime(self, tires: List[List[int]], change_time: int, num_laps: int) -> int:
        def pieces(tire: List[int], change_time: int) -> List[float]:
            r, f = tire
            pieces: List[float] = [change_time + r]
            
            i: int = 1
            while r * f ** i < change_time + r:
                pieces.append(change_time + r * round((1 - f**(i + 1)) / (1 - f)))
                i += 1
            
            return pieces


        def init_best_laps(tires: List[Tuple[int]]) -> List[float]:
            pieces_list: List[List[float]] = [pieces(tire, change_time) for tire in tires]
            best_laps: List[float] = []
            
            for i in range(max(len(pieces) for pieces in pieces_list)):
                best_laps.append(min(min(pieces[i] for pieces in pieces_list if len(pieces) > i), next_best_laps(best_laps)))
            
            return best_laps


        def next_best_laps(best_laps: List[float]) -> float:
            best: float = float('inf')
            
            for i in range(len(best_laps) // 2 + len(best_laps) % 2):
                best = min(best, best_laps[i] + best_laps[-i - 1])
            
            return best


        best_laps: List[float] = init_best_laps(tires)

        for i in range(min(int(len(best_laps)**1.8), num_laps - len(best_laps))):
            best_laps.append(next_best_laps(best_laps))
        
        if num_laps == len(best_laps):
            return best_laps[num_laps - 1] - change_time

        best: int = float('inf')
        for i in range(len(best_laps)):
            rest = num_laps % (i + 1)
            best = min(best, best_laps[i] * (num_laps // (i + 1)) + (0 if rest == 0 else best_laps[rest - 1]))
        
        return best - change_time
2799