Official

C - ビリヤードの反射 / Billiard Reflections Editorial by MMNMM


区間の両端に鏡を置いて、反射した鏡の向こうの区間も含めた数直線全体に対応する無限の長さの区間について考えます。

すると、区間の端でボールが反射する動きは、鏡の向こうからやってきたボールと入れ替わる動きだと思うことができます。 つまり、ボール(元あったボールおよび鏡の中の無限個のボール)は停止するまで同じ向きに動き続けていると考えることができます(AWC 0092 - B の解説も参考にしてください)。

座標 \(S\ (0\le S\le M)\) にあるボールが、右向きに \(A\) だけ進むことを考えます。 もとの世界でのボールの反射は、このボールが座標 \(M,2M,3M,\ldots\) にある鏡を通過することに対応します。 よって、このボールが反射する回数(通過する鏡の枚数)は、\(\left\lfloor\dfrac{\max\lbrace0,S+A-1\rbrace}M\right\rfloor\) となります。

左向きに進む場合についても同様に考えることができます(「座標 \(S\) のボールが左向きに進むこと」を「座標 \(-S\) などのボールが右向きに進むこと」に言い換えることで右向きの場合に帰着することもできます)。

ショットが終わった時点でのボールの座標は AWC0092 B と同様の計算で求めることができます。

よって、\(1\) 回のショットあたり \(O(1)\) 時間で反射回数とショットが終わった時点でのボールの座標を求めることができるので、全体で \(O(N)\) 時間で答えを求めることができます。

実装例は以下のようになります。

#include <iostream>
using namespace std;

int main() {
    int N, M;
    long S;
    cin >> N >> M >> S;
    long ans = 0;
    for (int i = 0; i < N; ++i) {
        char C;
        int A;
        cin >> C >> A;
        if (C == 'L') { // 左に動くことを
            S = -S; // -S にいるボールが右に動くことに言い換え
        }
        S += A;
        ans += max(0l, (C == 'L' ? S + M : S) - 1) / M; // 反射回数を足して
        S = (S + 2 * M) % (2 * M); // 位置を求める
        if (S > M) {
            S = 2 * M - S;
        }
    }
    cout << S << " " << ans << endl;
    return 0;
}
N, M, S = map(int, input().split())

ans = 0
for i in range(N):
    C, A = input().split()
    A = int(A)
    if C == 'L': # 左に動くことを
        S = -S # -S にいるボールが右に動くことに言い換え
    S += A
    ans += max(0, S + M * (C == 'L') - 1) // M # 反射回数を足して
    S = S % (2 * M) # 位置を求める
    if S > M:
        S = 2 * M - S

print(S, ans)