D - すごろくの旅 / A Journey of Sugoroku 解説 by admin
GPT 5.4 High概要
各マスから「次にどこへ行くか」は完全に一意に決まるので、この問題は「マス \(1\) から始めて、関数グラフを \(K\) 回たどった先を求める問題」です。
\(K\) は最大 \(10^{18}\) と非常に大きいですが、状態数は \(N\) 個しかないため、途中から必ず同じ動きの繰り返しになります。
考察
1. 各マスの次の行き先は 1 つに決まる
この問題では、現在いるマス \(i\) が分かれば、次のマスは必ず 1 つに定まります。
- \(i = N\) なら常に \(N\)
- \(i = 1\) なら
- \(A_1 + A_2\) が偶数 \(\Rightarrow 2\)
- 奇数 \(\Rightarrow 1\)
- \(2 \le i \le N-1\) なら
- \(A_i + A_{i+1}\) が偶数 \(\Rightarrow i+1\)
- 奇数 \(\Rightarrow i-1\)
つまり、各マスから出る辺がちょうど 1 本のグラフだと考えられます。
このようなグラフは競技プログラミングで 関数グラフ と呼ばれます。
2. 素朴に \(K\) 回シミュレーションすると間に合わない
そのまま \(K\) 回移動をシミュレーションすると、計算量は \(O(K)\) です。
しかし \(K \le 10^{18}\) なので、これは到底間に合いません。
3. 状態数は \(N\) 個しかないので、必ずループに入る
今の状態は「現在いるマス番号」だけで表せます。
マスは \(1\) から \(N\) までの \(N\) 個しかないので、同じマスに 2 回到達した瞬間、その後の動きは完全に繰り返しになります。
つまり、移動列は必ず
- 最初のループに入る前の部分
- その後ずっと繰り返す周期部分
の 2 つに分かれます。
たとえば移動の様子が
\(1 \to 2 \to 3 \to 2 \to 3 \to 2 \to 3 \to \cdots\)
なら、
- ループに入る前:\(1\)
- 周期部分:\(2, 3\)
です。
4. 同じマスを初めて訪れた時刻を記録する
これを利用するために、
visited[x]= マス \(x\) を最初に訪れた時刻order[t]= \(t\) 回移動した直後にいるマス
を記録しながら進みます。
ここでコードでは order[0] = 1 としており、
「0 回移動後はマス 1 にいる」 という形で扱っています。
もし今いるマス pos がすでに訪問済みなら、そこがループの開始点です。
- ループ開始位置を \(\mu\)
- ループ長を \(\lambda\)
とすると、
- \(\mu = \text{visited[pos]}\)
- \(\lambda = \text{現在の時刻} - \mu\)
で求まります。
その後、\(K\) 回後の位置は
- \(K\) がまだ記録済み範囲内なら
order[K] - そうでなければ周期部分に落として
order[mu + (K - mu) % lambda]
で求められます。
5. なぜこれでよいのか
同じマスに戻ってきたということは、そのマスから先の遷移も前回と全く同じです。
したがって、以後の動きは周期 \(\lambda\) で繰り返されます。
よって、大きな \(K\) も「ループ部分のどこにいるか」だけ分かれば十分で、
\(K\) をそのまま扱う必要はありません。
アルゴリズム
- \(N=1\) なら、何回移動しても答えは常に \(1\)。
- 各マス \(i\) について、次のマス
nxt[i]を前計算する。 pos = 1から始めて、以下を繰り返す。visited[pos] == -1ならvisited[pos] = steporder.append(pos)pos = nxt[pos]step += 1
- すでに訪れたマスに来たらループを発見したことになる。
mu = visited[pos]cycle_len = step - mu
- 答えを求める。
K < len(order)ならorder[K]- それ以外なら
order[mu + (K - mu) % cycle_len]
計算量
- 時間計算量: \(O(N)\)
- 空間計算量: \(O(N)\)
実装のポイント
配列を 1-indexed にすると、問題文のマス番号とそのまま対応して実装しやすいです。
order[0]は「0 回移動後」の位置であることに注意してください。これにより、答えがそのままorder[K]の形で扱えます。偶奇判定は
((A[i] + A[i+1]) & 1) == 0で行っています。ループ検出は「同じマスに再び来たら終了」で十分です。各マスからの遷移先が 1 つに決まっているためです。
ソースコード
import sys
def main():
data = list(map(int, sys.stdin.buffer.read().split()))
N, K = data[0], data[1]
A = [0] + data[2:]
if N == 1:
print(1)
return
nxt = [0] * (N + 1)
nxt[N] = N
if ((A[1] + A[2]) & 1) == 0:
nxt[1] = 2
else:
nxt[1] = 1
for i in range(2, N):
if ((A[i] + A[i + 1]) & 1) == 0:
nxt[i] = i + 1
else:
nxt[i] = i - 1
visited = [-1] * (N + 1)
order = []
pos = 1
step = 0
while visited[pos] == -1:
visited[pos] = step
order.append(pos)
pos = nxt[pos]
step += 1
mu = visited[pos]
cycle_len = step - mu
if K < len(order):
print(order[K])
else:
print(order[mu + (K - mu) % cycle_len])
if __name__ == "__main__":
main()
この解説は gpt-5.4-high によって生成されました。
投稿日時:
最終更新: