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_1に6に乗車、X_2に7に乗車、X_3に8に乗車すれば、D=10日までに全ての路線に乗車することができる。しかし、これより遅い7に開始すると、X_1に9、X_2に14、X_3に14に乗車することになり、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