0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

落ちものシミュレーション (※短絡評価)

Posted at

今回は paiza の「落ちものシミュレーション」の問題に挑戦!


🧩 問題概要

何をする問題か

  • H × 横 W のフィールド上で
  • 長方形ブロックが上から順番に落ちてくる
  • 各ブロックは
    • 下に他のブロック または
    • フィールドの底
      に接触した瞬間に その位置で固定

入力

フィールド情報

  • H:縦のサイズ(行数)
  • W:横のサイズ(列数)
  • N:落ちてくるブロックの数

各ブロックの情報

  • h_i:ブロックの高さ
  • w_i:ブロックの幅
  • x_i:左端の位置(0-index)

※ すべて フィールド内に収まることは保証されている

H W N
h_1 w_1 x_1
h_2 w_2 x_2
...
h_N w_N x_N

ブロックの挙動

  • ブロックは 回転しない
  • 横位置 x_i は固定
  • 上から落ちてきて、これ以上 下に行けなくなった行で止まる
  • 止まったら、その位置に # でブロックを描画

出力

  • 最終的なフィールドを 上から順に .(空)と #(ブロック)で出力
  • 行数は H
  • 各行の文字数は W



入力例:

7 10 4
1 8 1
4 1 5
1 6 2
2 2 0

出力例:

..........
..######..
.....#....
.....#....
##...#....
##...#....
.########.






✅OK例:

// 標準入力を受け取るための readline 設定
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);

    // 各ブロックの情報 [高さ, 幅, 左端の列]
    const blocks = lines.slice(1).map(b => b.split(' ').map(Number));
    
    // フィールドを '.' で初期化
    const field = Array.from({ length: H }, () => Array(W).fill('.'));
    
    // 指定された位置にブロックを配置する関数
    function put(h, w, x, bottom) {
        // bottom 行を底として、上に h 行分 '#'' を埋める
        for (let i = bottom - h + 1; i <= bottom; i++) {
            for (let j = x; j < x + w; j++) {
                field[i][j] = '#';
            }
        }
    }
    
    // ブロックを1つずつ落とす
    for (const b of blocks) {
        const [h, w, x] = b;
        
        // 上から順に落下位置を探す
        LABEL:
        for (let i = 0; i < H; i++) {
            // ブロックの幅分、下に置けるか確認
            for (let j = x; j < x + w; j++) {
                // 底に到達、または下にブロックがあれば確定
                if (i + 1 >= H || field[i+1][j] === '#') {
                    put(h, w, x, i); 
                    break LABEL; // このブロックの処理を終了
                }
            }
        }
    }
    
    // 完成したフィールドを出力
    field.forEach(f => console.log(f.join('')));
});

🔍コードの流れ

  • 標準入力からすべての行を lines 配列に格納
  • 1行目から
    • フィールドの高さ H
    • W
    • ブロック数 N
      を取得
  • 2行目以降をブロック情報 [h, w, x] の配列として取得

フィールド初期化

  • 高さ H、幅 W の2次元配列 field を作成
  • すべてのマスを '.'(空)で初期化

put 関数

  • 引数:
    • h:ブロックの高さ
    • w:ブロックの幅
    • x:左端の列
    • bottom:ブロックの底になる行
  • bottom - h + 1bottom の範囲に、幅 w'#' を埋める

ブロックを1つずつ落とす処理

  • 各ブロック [h, w, x] について以下を行う

落下位置の探索

  • 上から下へ i = 0H-1 まで調べる
  • ブロック幅分のすべての列 j = xx + w - 1 を確認

停止条件

  • 次の行がフィールド外 または
  • 次の行に '#' があるマスを見つけたら
    • その i 行をブロックの底として確定
    • put() でブロックを配置
    • break LABEL で探索終了

出力

  • フィールドを上から順に文字列として出力




💡 補足:短絡評価

i + 1 >= H を先に書いて範囲外アクセスを回避する!


↓初めにこう書いてエラーが出た

if (field[i+1][j] === '#' || i + 1 >= H)

↓ 修正例:

i + 1 >= H || field[i+1][j] === '#'

||(短絡評価) は左が true になった瞬間、右を一切評価しないから、範囲外アクセスを先に書くと、エラーが出ず意図したとおりに動く。






📝まとめ

① シミュレーション問題の基本構造

  • 状態(field)を二次元配列で持つ
  • 入力を 順番通りに処理
  • 1つ処理するごとに状態を更新

② 「ブロック単位」で考える

  • マス単位で処理しない
  • ブロック全体が置けるかを判定
  • 置く処理は 1回だけ

③ 二重ループの役割分担

  • 外側:高さ方向(落下)
  • 内側:幅方向(衝突チェック)

④ 境界条件の扱い

  • 配列アクセス前に 必ず範囲チェック
  • i + 1 >= H を先に書く
i + 1 >= H || field[i+1][j] === '#'

⑤ 短絡評価

  • || は左が true なら右を評価しない
  • 危険な配列アクセスは 右側

⑥ 「止まる瞬間」を正しく捉える

  • 「置けるか?」ではなく
  • 「次に進めなくなる瞬間」を検出

⑦ ラベル / break の使いどころ

  • 二重ループを一気に抜けたい場面
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?