今回は paiza の「ビームの反射」の問題に挑戦!
ここまでの問題で身につけてきた知識や考え方を活用して解いていく!
問題概要
箱の内部が 格子状の区画 に分けられており、
各区画には以下のいずれかが配置されている:
〇 記号:意味
-
_:鏡なし(そのまま通過) -
/:右上↙左下方向の鏡(/ 形) -
\:左上↘右下方向の鏡(\ 形)
🌀 ビームの動き
- ビームは 左上の区画の左側外部 から 右方向(→) に発射される。
- 進行中、各区画に入るたびに:
- 区画を 1回通過した としてカウント。
- 鏡があれば 反射 して方向を変える。
- 箱の 外に出た時点 で終了。
🧮 出力内容
- ビームが外に出るまでに 通過した区画の数 を出力。
※ 環境によりバックスラッシュが円マークで表示される。その場合は文字コードによるバグを防ぐため、ソースコードに記述する際はサンプル入力から円マークをコピー&ペーストすることを推奨。
入力例:
3 5
__\_/
___/_
\/\_/
出力例:
9
✅OK例:
const rl = require('readline').createInterface({ input: process.stdin });
const lines = [];
rl.on('line', (input) => lines.push(input));
rl.on('close', () => {
const [H, W] = lines[0].split(' ').map(Number);
const s = lines.slice(1).map(line => line.split(''));
let x = 0;
let y = 0;
let dx = 1; // 初期方向は右向き
let dy = 0; // 右 (+1, 0)、左 (-1, 0)、上 (0, -1)、下 (0, +1)
let count = 0;
while (true) {
// 範囲外に出たら終了
if (x < 0 || x >= W || y < 0 || y >= H) break;
const cell = s[y][x];
count++;
if (cell === '/') {
// 「/」の場合:dx, dy を入れ替えて両方の符号を反転
[dx, dy] = [-dy, -dx];
} else if (cell === '\\') {
// 「\」の場合:dx, dy を入れ替える
[dx, dy] = [dy, dx];
}
// 次の位置に進む
if (dx === 1 && dy === 0) x++;
else if (dx === -1 && dy === 0) x--;
else if (dx === 0 && dy === -1) y--;
else if (dx === 0 && dy === 1) y++;
}
console.log(count);
});
💡 コードの流れ
- 標準入力の読み取り準備
-
readlineモジュールを使って入力を受け取る。 - すべての行を
lines配列に保存。
-
- 入力の整形
- 1行目から高さ
Hと幅Wを取得。
(例:"3 5"→H=3, W=5) - 2行目以降を文字配列に分解して、箱の中の鏡配置を
sに格納。
(2次元配列になる)
- 1行目から高さ
- 初期状態の設定
-
x = 0,y = 0→ 左上の区画から開始。 -
dx = 1,dy = 0→ 右向きにビームを発射。 -
count = 0→ 通過した区画の数を数えるカウンタ。
-
- ビームの進行をループでシミュレーション
- 無限ループ
while (true)を開始。
- 無限ループ
- 箱の外に出たら終了
-
xまたはyが範囲外になったらbreak。
-
- 現在の区画の状態を確認
-
cell = s[y][x]で現在位置の文字を取得。 -
count++して通過回数をカウント。
-
- 鏡に当たった場合の反射処理
-
cell === '/'のとき →[dx, dy] = [-dy, -dx]
(軸を入れ替えて符号を反転) -
cell === '\\'のとき →[dx, dy] = [dy, dx]
(軸を入れ替えるだけ)
-
- 進行方向に1マス移動
- 現在の
dx,dyに応じてxまたはyを更新。 - 右なら
x++、左ならx--、上ならy--、下ならy++。
- 現在の
- ループに戻って次の区画へ進む
- 箱の外に出た時点でループ終了し、結果を出力
-
console.log(count)で通過区画の総数を出力。
-
⚡ 改善できるワンポイント(よりシンプル化)
while (x >= 0 && x < W && y >= 0 && y < H) {
const cell = s[y][x];
count++;
if (cell === '/') [dx, dy] = [-dy, -dx];
else if (cell === '\\') [dx, dy] = [dy, dx];
// ⚡
x += dx;
y += dy;
}
👉 if で4方向に分岐しなくてもOK。
dx, dy の値自体が進行方向を持っているので。
✨ 学習のまとめ
- 方向をベクトル (
dx,dy) で管理する
→ 向きごとの条件分岐(右・左・上・下)を1行で統一できる。 - 鏡の反射は座標変換で表せる
- 鏡:計算式:説明
-
/:[dx, dy] = [-dy, -dx]:軸を入れ替えて符号反転 -
\:[dx, dy] = [dy, dx]:軸を単に入れ替える
- 終了条件は「範囲外チェック」だけでOK
- ループ内で「通過カウント → 反射判定 → 移動」を順に行う