IT1-CODE-POCKET

シミュレーション型の問題の読み方

レジの行列、担当者への仕事の割り当て、来場者数の累計—— 第3問の後半に出てくる長い問題文の問題です。 一見むずかしそうですが、やっていることはここまでの型の組み合わせにすぎません。 怖がらずに表を書き始められるかどうかが勝負です。

APPROACH

長い問題文にどう向き合うか

シミュレーション型は、問題文が長く、身近な題材(お店、学校行事、当番表)で書かれています。 そのため「読解問題」に見えますが、実際に使う情報はごく一部です。 次の3ステップで機械的に処理します。

  1. 登場する配列と変数を書き出す … Arrive、service、Start のように、何を表すかを1行でメモする
  2. 1周でやることを1文にする … 「開始時刻を決めて、終了時刻を出す」のように短くまとめる
  3. 表を作って1行ずつ埋める … あとはトレースと同じ作業
物語を理解しようとしないでください。 「なぜ4分かかるのか」を考える必要はありません。 数字がどう変わるかだけを追えば答えは出ます。

出てくるパターンは4つだけ

パターン 中心になる式 題材の例
順番待ち 最大値(到着, 前の終了) レジ、窓口、順番待ち
割り当て いちばん小さい担当者を探して足す 当番、作業分担、係決め
累積で判定 total を足しながら上限と比べる 来場者数、定員、予算
連続の長さ 続けばカウント、切れたら0に戻す 連勤、連続記録、空き状況

EXERCISE 1

例題1:順番待ちの開始時刻

1つの窓口で順番に処理します。i番目の人の開始時刻 Start[i] を正しく求めるには、[ ア ]に何を入れればよいですか。

Arrive = [0, 2, 4, 9, 11]
service = 5
Start = [0, 0, 0, 0, 0]
Finish = [0, 0, 0, 0, 0]

Start[0] = Arrive[0]
Finish[0] = Start[0] + service

i を 1 から 要素数(Arrive) - 1 まで 1 ずつ増やしながら繰り返す:
|Start[i] = [ ア ]
⎿ Finish[i] = Start[i] + service

表示する(Start)

選択肢:① 最大値(Arrive[i], Finish[i - 1]) ② 最小値(Arrive[i], Finish[i - 1]) ③ Arrive[i] + service ④ Finish[i - 1] - service

答えと解説を見る

答え:① 最大値(Arrive[i], Finish[i - 1])

対応を始めるには2つの条件が同時にそろう必要があります。

  • その人が到着していること … Arrive[i] 以降
  • 前の人が終わっていること … Finish[i-1] 以降

両方がそろう時刻は、2つのうち遅いほうです。だから最大値を使います。 「待ち行列なのに最大値?」と違和感を持つ人が多いところですが、 「遅いほうに合わせる=最大値」と覚えてください。

i到着前の終了開始終了待ち
00050
1255103
241010156
391515206
4112020259

② の最小値を選ぶと、前の人が終わる前に始めてしまうことになり、現実に合いません。 ③ は到着してすぐ終わることになり、④ は時間が巻き戻ります。

待ち時間 = 開始 − 到着です。引く順番を逆にすると負の数になります。 この処理の動きは待ち行列シミュレーターで1人ずつ確認できます。

EXERCISE 2

例題2:累積が上限を超える時点

次のプログラムを実行したとき、表示される day の値として正しいものを選びなさい。

Visitors = [18, 22, 15, 30, 12, 20]
limit = 70
total = 0
day = -1

i を 0 から 要素数(Visitors) - 1 まで 1 ずつ増やしながら繰り返す:
|total = total + Visitors[i]
|もし total >= limit かつ day == -1 ならば:
⎿⎿ day = i

表示する(day)

選択肢:① 2 ② 3 ③ 4 ④ 5

答えと解説を見る

答え:② 3

これは累積和と、-1 を見張り番に使う テクニックの組み合わせです。新しい考え方は何も出てきません。

i人数累計70以上?day
01818×-1
12240×-1
21555×-1
330853
412973 のまま
5201173 のまま

初めて70以上になるのは i = 3 のときです。 day == -1 の条件があるおかげで、それ以降は上書きされません。

もし day == -1 がなければ、最後の i = 5 で上書きされて 答えは 5 になります。④ はその間違いを選ばせる選択肢です。 条件式に「かつ」でつながれた部分があったら、必ず役割を確認してください。

また、答えは「4日目」ではなく添字の 3 です。 問題文が「何日目か」と聞いていれば 4 と答える必要があります。 添字を答えるのか、順番を答えるのかを必ず確認してください。

EXERCISE 3

例題3:連続している区間の長さ

次のプログラムを実行したとき、表示される longest の値として正しいものを選びなさい。

Busy = [1, 1, 0, 1, 1, 1, 0, 1]
now = 0
longest = 0

i を 0 から 要素数(Busy) - 1 まで 1 ずつ増やしながら繰り返す:
|もし Busy[i] == 1 ならば:
||now = now + 1
||もし now > longest ならば:
|⎿⎿ longest = now
|そうでなければ:
⎿⎿ now = 0

表示する(longest)

選択肢:① 2 ② 3 ③ 5 ④ 6

答えと解説を見る

答え:② 3

この形は2つの箱を使い分けるのが要点です。

  • nowいま続いている長さ。途切れたら 0 に戻す
  • longestこれまでの最長。0 に戻さない
iBusy[i]nowlongest
0111
1122
2002
3112
4122
5133
6003
7113

1が続く区間は「2個」「3個」「1個」の3か所で、最長は 3 です。

③ の5は、1の総数(2+3+1 = 6ではなく、途中まで数えた場合)を答えた間違い、 ④ の61の個数を全部数えた間違いです。 聞かれているのは「連続している長さ」であって「個数」ではありません。

「そうでなければ now = 0」を見落とすと、ただの個数になってしまいます。 この1行があるかどうかで意味がまったく変わるため、 リセットの行を探すのがこの形を見抜くコツです。

ASSIGN

割り当て問題は「最小値を探して足す」

当番や作業の割り当ては、いちばん空いている人を探して、その人に仕事を足すという繰り返しです。 最小値の探索と集計を組み合わせた形になります。

Days = [4, 1, 3, 1, 3, 4]
Available = [1, 1, 1]

work を 0 から 要素数(Days) - 1 まで 1 ずつ増やしながら繰り返す:
|member = 0
|i を 1 から 要素数(Available) - 1 まで 1 ずつ増やしながら繰り返す:
||もし Available[i] < Available[member] ならば:
|⎿⎿ member = i
⎿ Available[member] = Available[member] + Days[work]

表示する(Available)

内側のループが「いちばん小さい人を探す」部分、 最後の1行が「その人に足す」部分です。 member = 0 が外側ループの中にあることに注目してください。 仕事ごとに探し直すため、ここでリセットする必要があります。

仕事日数選ばれる人Available
開始[1, 1, 1]
040[5, 1, 1]
111[5, 2, 1]
232[5, 2, 4]
311[5, 3, 4]

同じ値が並んだときは、先に見つけたほうが選ばれます。 条件が < なので、同じ値では更新されないためです。 <= にすると後のほうが選ばれ、答えが変わります。ここも問われやすい点です。

EXERCISE 4

例題4:全部そろったら打ち切る条件

座席を前から順にグループへ割り当てます。 すべてのグループが必要数を満たしたら繰り返しを抜けるには、[ ア ]に何を入れればよいですか。

Seats = [2, 4, 1, 3, 2]
Need = [3, 5, 2]
Filled = [0, 0, 0]
group = 0

i を 0 から 要素数(Seats) - 1 まで 1 ずつ増やしながら繰り返す:
|Filled[group] = Filled[group] + Seats[i]
|もし Filled[group] >= Need[group] ならば:
|⎿ group = group + 1
|もし [ ア ] ならば:
⎿⎿ 繰り返しを抜ける

表示する(group)

選択肢:① group == 要素数(Need) ② group == 要素数(Seats) ③ Filled[group] == 0 ④ i == group

答えと解説を見る

答え:① group == 要素数(Need)

まず、変数が何を表しているかを1行ずつメモします。ここを飛ばすと必ず迷います。

  • Seats … 前から順に来る座席のかたまり
  • Need … 各グループが必要とする座席数(3人分・5人分・2人分)
  • Filled … 各グループに今まで入った数
  • group今どのグループに入れているか。同時に「完了した数」でもある

鍵は group です。 必要数に届くと group = group + 1 で次へ進むので、 この値は「終わったグループの数」と同じになります。 グループは全部で 要素数(Need) 個なので、 group がその値になった=全部終わったということです。

iSeats[i]入れる先Filledgroup
開始前[0, 0, 0]0
02group 0[2, 0, 0]0
14group 0[6, 0, 0]1(3以上で完了)
21group 1[6, 1, 0]1
33group 1[6, 4, 0]1
42group 1[6, 6, 0]2(5以上で完了)

この例では Seats を使い切っても group は 2 までで、 3つ目のグループは完了しません。表示されるのは 2 です。 つまり「抜ける」条件は一度も成立しない——それでもプログラムとしては正しい、という点も確認しておいてください。

  • ② group == 要素数(Seats) … 比べる相手が違います。 Seats は座席のかたまりの個数(5)で、グループ数ではありません。 2つの配列が出てきたとき、どちらの個数と比べるのかが最大の分かれ目です
  • ③ Filled[group] == 0 … 開始直後に成り立ってしまい、1回目で抜けてしまいます
  • ④ i == group … i は座席の番号、group はグループ番号で、比べる意味がありません
「打ち切り条件」は、ゴールを言葉にしてから式にしてください。 ここでのゴールは「全グループが完了した」。 それを式にすると「完了数 == グループ数」になります。 日本語を先に固めれば、②のようにもっともらしい別の配列を選ぶ間違いを防げます。

EXERCISE 5

例題5:前の日と比べる

前の値より大きくなった回数を数えるには、[ ア ]に何を入れればよいですか。

Temps = [18, 20, 19, 22, 22, 25]
count = 0

i を 1 から 要素数(Temps) - 1 まで 1 ずつ増やしながら繰り返す:
|もし [ ア ] ならば:
⎿⎿ count = count + 1

表示する(count)

選択肢:① Temps[i] > Temps[i - 1] ② Temps[i] < Temps[i - 1] ③ Temps[i] == Temps[i - 1] ④ Temps[i] >= Temps[i - 1]

答えと解説を見る

答え:① Temps[i] > Temps[i - 1]

日ごとのデータを扱う問題では、「前の日」と比べる形が非常によく出ます。 まず注目したいのは、繰り返しが 0 ではなく 1 から始まっている点です。

1日目には「前の日」が存在しないからです。 もし 0 から始めると Temps[-1] という存在しない場所を見ることになります。 「− 1 を使うなら 1 から始める」—— 繰り返しの回数で扱った「+ 1 を使うなら1つ手前まで」とちょうど対になる形です。

i前日当日増えた?count
118201
22019×1
319222
42222×(同じ)2
522253

答えは 3 です。 i = 4 で 22 と 22 が並ぶように、データがわざわざ作られている点に注目してください。 同じ値のときにどうするかを試すための仕掛けです。

  • ④ >= を選ぶと 4 になります。「増えた」に「変わらなかった」を含めてしまうためです
  • ② < … 下がった回数(1回)になります
  • ③ == … 同じだった回数(1回)になります
データに同じ値が並んでいたら、出題者が等号を試しています。 「増えた」なのか「減らなかった」なのか、問題文の日本語をもう一度読み直してから不等号を決めてください。 トレースは合っているのに1だけずれる失点は、ほぼこれが原因です。

SUMMARY

型の組み合わせで読む

ここまで見たとおり、シミュレーション型に新しい文法は出てきません。 使われているのは、これまでの5テーマの型だけです。

問題の種類 使われている型
順番待ち 配列の読み方 + 前の要素の参照(i - 1)
累積で判定 集計(累積和)+ -1 を見張り番に使う
連続の長さ 集計(カウント)+ 最大値の更新 + リセット
割り当て 最小値の探索 + 集計 + 二重ループ

問題文が長くても、コードの中で使われている型を1つずつ見分けていけば読み解けます。 逆にいえば、ここまでの5テーマが身についていれば、発展問題も特別な準備は要りません。

最後に必ず確認すること: 聞かれているのが添字か、順番か、値か。 「何日目」「何人目」と聞かれたら添字に1を足す必要があります。 正しくトレースできたのに、ここで1つずれて失点するのが一番もったいないパターンです。

CHECK

このテーマのチェックリスト

6テーマすべてを読み終えたら、あとは演習です。 クイズの「発展」で210問のうちの発展30問に挑戦してみてください。

次に読むページ