今回は paiza の「いびつなひとりリバーシ」の問題に挑戦!
🧩 問題概要
盤面とマスの種類
-
H×Wの盤面が与えられる - 各マスは次のいずれか
-
'*':すでに置かれている自分の石 -
'.':何も置かれていないマス -
'#':穴の空いているマス(通過・反転不可)
-
操作内容(これを N 回繰り返す)
- あらかじめ与えられた座標 (
Y_i,X_i) に石を置く- そのマスは必ず
'.'(空マス)
- そのマスは必ず
- 石を置いた直後、その石を起点に、縦・横・斜めの8方向 に探索を行う
- ある方向について次を満たした場合のみ石を置ける
- 穴
'#'を含まない連続したマス列 - 自分の石
'*'が存在する(=はさめている)
- 穴
- はさめた場合
- 間にあるマスをすべて
'*'に変える
- 間にあるマスをすべて
- 新しく石を置いた結果、さらに置ける状態になっても
- その操作はそこで終了
やること
- 上記の操作を
N回すべて順番に実行する - 最終的な盤面を
H行で出力する
入力例:
3 3 1 // H W N
..*
...
*.*
0 0 // Y_i X_i
出力例:
***
**.
*.*
✅ OK例:
const rl = require('readline').createInterface({ input: process.stdin });
const lines = [];
// 入力を1行ずつ配列に保存
rl.on('line', (line) => lines.push(line));
rl.on('close', () => {
// H:高さ, W:幅, N:操作回数
const [H, W, N] = lines[0].split(' ').map(Number);
// 盤面を2次元配列に変換
const grid = lines.slice(1, H + 1).map(row => row.split(''));
// 石を置く座標リスト
const stones = lines.slice(H + 1).map(row => row.split(' ').map(Number));
// 8方向(縦・横・斜め)
const directionList = [
[-1, 0], // 上
[-1, 1], // 右上
[0, 1], // 右
[1, 1], // 右下
[1, 0], // 下
[1, -1], // 左下
[0, -1], // 左
[-1, -1] // 左上
];
// 各操作ごとに処理
for (const stone of stones) {
const [Y, X] = stone;
// 穴(#)でなければ石を置く
if (grid[Y][X] !== '#') grid[Y][X] = '*';
else continue;
// 8方向をチェック
for (const direction of directionList) {
const [dy, dx] = direction;
let ny = Y + dy;
let nx = X + dx;
const path = []; // 挟めるか確認するための通過マス
// 盤面内を進む
while (0 <= ny && ny < H && 0 <= nx && nx < W) {
if (grid[ny][nx] === '#') break; // 穴があれば失敗
if (grid[ny][nx] === '*') {
// 自分の石で挟めたら、通過したマスを石に変える
for (const [py, px] of path) {
grid[py][px] = '*';
}
break;
}
// 空マスを記録して次へ
path.push([ny, nx]);
ny += dy;
nx += dx;
}
}
}
// 最終盤面を出力
grid.forEach(g => console.log(g.join('')));
});
🔍 コード全体の流れ
- 最初の行から
- 盤面サイズ
H, W - 操作回数
Nを取得
- 盤面サイズ
- 次の
H行を、文字の2次元配列grid(盤面)に変換 - 残りの行を、石を置く座標
(Y, X)の配列として取得 - 各操作について:
- 指定マスに
*を置く(穴#ならスキップ) - 8方向それぞれに石の位置から探索
-
#が出たらその方向は失敗 -
*が出たら、その間に通ったマスをすべて*に変える
-
- 指定マスに
- 全操作終了後、盤面をそのまま出力
🗒️ まとめ
🔹 1. 「1操作 = 完結した処理」を `N` 回回す - 1回の処理ロジックをそのまま `for` ループで包む - 状態(盤面)は 累積的に更新 される
🔹 2. 8方向探索ロジックの再利用
- Step6 で作ったロジックをそのまま使い回せる:
- 方向リスト
-
whileによる直線探索 -
pathによる一時保存
→ 問題がレベルアップしても、「考え方」は変わっていないことが分かる
🔹 3. 途中で確定させない設計の重要性
- 通過マスは一旦
pathに保存 -
'*'に到達したときだけ反映 - 失敗時(
'#'や盤面外)は何もしない
→ ロジックが安全・シンプルになる
🔹 4. 問題文の「連鎖しない」に注意
- ひっくり返した結果さらに石をはさめる状況が生まれても、再探索しない
- 「1回の操作で見るのは、置いた石起点のみ」
🔹 5. 入力処理の整理
- 入力を分ける:
- 盤面情報
- 操作情報
-
slice()の使い分けで問題文どおりの構造をそのままコードに反映