🎃

Kickstart2020 RoundB: Bus Route

2021/05/22に公開

https://codingcompetitions.withgoogle.com/kickstart/round/000000000019ffc8/00000000002d83bf

問題

N個のバス路線(Bus Route)とバス旅の終了日Dが与えられます。N個のバス路線を乗り継いで、D日までに全てのバスを乗り終えるとしたとき、最も遅いバス旅の開始日を求めるというのが問題。ただしi番目のバス路線をX_iとすると、乗車可能日はX_i, 2X_i, 3X_iのようにX_i日毎となる。

例で考える。N=3, D=10でバス路線がX_1=3, X_2=7, X_3=2と与えられたとすると、X_1の乗車可能日は3,6,9,...で、同様にX_2の乗車可能日は、7,14,21,...X_3の乗車可能日は2,4,6,8,10,...となる。この場合、最も遅い開始日は6で、X_16に乗車、X_27に乗車、X_38に乗車すれば、D=10日までに全ての路線に乗車することができる。しかし、これより遅い7に開始すると、X_19X_214X_314に乗車することになり、D=10に間に合わない。8以降も同様。よって、この問題の解は6となる。

アルゴリズムと実装

D日までに終える必要があるので、開始日の候補は1からDまでとなる。また、上記の例で、例えば解となる日の6よりも前の5も同じようにD=10までにX_3まで乗車することが可能。したがって、1からDまでで、Dまでに全路線に乗車可能な候補日区間の上限を求めれば良さそう。

ある開始日Kとしたとき、i=1番目のバス路線に乗車可能な日はどう計算するか。K以上になるようなX_1の倍数を求める\lceil \frac{K}{X_1} \rceil * X_1で計算することができる。同様に、i+1番目のバス路線に乗車可能な日は、\lceil \frac{D_{i}}{X_{i+1}} \rceil * X_{i+1}と求めることができる。候補日の末尾Dから、N個のバス路線に乗車した日を計算して、N番目のバス路線の乗車日がD以内かを判定する処理を繰り返して、最初に条件を満たす日が解となる。この場合の計算量はO(DN)
Test set2では1 \leq D \leq 10^{12}であるため、より効率的なBinary Searchを使って繰り返し処理を行う。通常、midの計算時にはleftとrightの合計から中間位置を計算しますが、切り捨ての結果midがleftになり、その時に条件を満たした場合に無限ループになってしまうため、midの計算は切り上げてmidが移動するようにしています。この場合の計算量はO(N\log{}D)です。
Test set1とTest set2のどちらもPassedとなりました。

import math

def binary_search(N, D, Xs, left, right):
    while left < right:
        mid = (left + right + 1) // 2
        days = [math.ceil(mid / Xs[0]) * Xs[0]]
        for i in range(1, N):
            days.append(math.ceil(days[i-1]/Xs[i]) * Xs[i])
        if days[-1] <= D:
            left = mid
        else:
            right = mid - 1
    return left

T = int(input())
for t in range(1, T + 1):
    N, D = list(map(int, input().split()))
    Xs = list(map(int, input().split()))
    res = binary_search(N, D, Xs, 1, D)
    print('Case #{}: {}'.format(t, res))

Discussion