B - 円形カード回し / Circular Card Rotation Editorial by admin
Gemini 3.0 Flash (Thinking)概要
円形に並んだ \(N\) 人の子供たちが、持っているカードを隣の人に渡す操作を \(K\) 回繰り返した後の状態を求める問題です。\(K\) が非常に大きいため、愚直なシミュレーションではなく数学的な規則性を見つける必要があります。
考察
1. カードの動きを観察する
例えば \(N=3\) のとき、カードがどのように動くか考えてみましょう。 - 最初:子供 1 はカード 1、子供 2 はカード 2、子供 3 はカード 3 を持っています。 - 1回目:全員が時計回りに渡すので、 - 子供 1 は子供 3 からカード 3 を受け取る - 子供 2 は子供 1 からカード 1 を受け取る - 子供 3 は子供 2 からカード 2 を受け取る - 2回目:さらに時計回りに渡すと、 - 子供 1 は子供 3 からカード 2 を受け取る - 子供 2 は子供 1 からカード 3 を受け取る - 子供 3 は子供 2 からカード 1 を受け取る
このように、操作を 1 回行うごとに、各子供が持っているカードの番号は「反時計回りに 1 つ隣の人が持っていたもの」に変わっていきます。
2. 素朴なアプローチの限界
操作を 1 回行うのに \(O(N)\) の時間がかかります。これを \(K\) 回繰り返すと、全体の計算量は \(O(NK)\) となります。 本問題では \(N \leq 2 \times 10^5\)、\(K \leq 10^{18}\) であるため、\(O(NK)\) では到底制限時間に間に合いません。\(K\) の値に依存しない、あるいは \(K\) が非常に大きくても高速に計算できる方法が必要です。
3. 数式での表現
\(K\) 回の操作後、子供 \(i\) が持っているカードが何番であるかを直接計算します。 カードが時計回りに \(K\) 回移動するということは、子供 \(i\) の位置には、もともと \(K\) 個手前(反時計回り側) にいた子供のカードがやってくることになります。
これを数式(0-indexed、つまり 0 から \(N-1\) 番として計算)で表すと、子供 \(i\) が持っているカードの初期位置は以下のようになります。 $\((i - K) \pmod N\)$
Python などの言語では、負の数に対する剰余演算(%)が数学的な定義通り(結果が \(0\) 以上 \(N-1\) 以下になるよう)に行われるため、この式をそのまま利用できます。
アルゴリズム
- 1 番目から \(N\) 番目までの各子供 \(i\) について、以下の計算を行います。
- 計算を簡単にするため、一旦 0-indexed に直します:
current_pos = i - 1 - \(K\) 回操作した後にその位置に来るカードの初期位置を求めます:
start_pos = (current_pos - K) % N - 1-indexed に戻すために 1 を加算します:
card_number = start_pos + 1 - 全員の計算結果を順に出力します。
計算量
- 時間計算量: \(O(N)\) \(N\) 人の子供それぞれについて定数時間で計算が終わるため、全体の計算量は \(N\) に比例します。\(K\) がどれほど大きくても計算時間は変わりません。
- 空間計算量: \(O(N)\) 結果を保持してまとめて出力する場合、リスト等に \(N\) 要素分を格納するため \(O(N)\) となります。
実装のポイント
大きな \(K\) の扱い: \(K\) は最大 \(10^{18}\) と非常に大きいですが、剰余演算を用いることで問題なく扱えます。
高速な入出力: \(N\) が最大 \(2 \times 10^5\) と比較的大きいため、Python では
sys.stdin.readやsys.stdout.writeを使用して入出力を高速化するのが無難です。負の数の剰余:
(i - 1 - K) % Nの計算において、i - 1 - Kが負になることがありますが、Python の%演算子は期待通り \(0\) 以上 \(N-1\) 以下の値を返してくれます。他の言語(C++ など)では負の結果が返ることがあるため、(i - 1 - K % N + N) % Nのように書く必要があります。ソースコード
import sys
def solve():
# 入力を取得
input_data = sys.stdin.read().split()
if not input_data:
return
n = int(input_data[0])
k = int(input_data[1])
# K回操作した後の子供 i (1-indexed) が持っているカードは、
# 最初、子供 (i - 1 - K) % N + 1 が持っていたカードである。
# 最初の時点では子供 x はカード x を持っているため、
# 答えは ((i - 1 - K) % N) + 1 となる。
results = []
for i in range(1, n + 1):
# 0-indexed で計算し、最後に 1 を足して 1-indexed に戻す
card_index = (i - 1 - k) % n
results.append(str(card_index + 1))
# 結果を出力
sys.stdout.write('\n'.join(results) + '\n')
if __name__ == '__main__':
solve()
この解説は gemini-3-flash-thinking によって生成されました。
posted:
last update: