Day 151-Q1 — 大洪水法(Great Deluge Algorithm)

2026-09-12 赤色 Master / Phase 8+ ★★★★★★★★★ 水位という決定的な1本の閾値だけで改悪の受理・拒否を判定するメタヒューリスティック局所探索

問題

数直線上に $N$ 個の地点が一列に並んでいる(1-indexed、地点 $i$ の高さは $A_i$)。地点 $i$ から移動できるのは隣接地点 $i-1, i+1$(範囲内のみ)に限られる。開始地点 $S$、水位下降率 $B$、最大反復回数 $T$ が与えられる。以下の大洪水法の手続きに従って探索し、実際に移動が成功した回数と、探索終了時点の地点・高さを出力せよ。

手続き

  1. 現在地点を $\mathrm{pos} \gets S$、水位を $L \gets A_S$ で初期化する。
  2. $\mathrm{it}=1,\dots,T$ の順に:水位を下げる($L \gets L-B$)→ 隣接候補のうち高さ$\le L$のものを実行可能とする → 実行可能な候補が無ければその場で終了(失敗、成功回数に含めない)→ あれば高さ最小(同点は地点番号最小)へ移動(成功)。

水位が反復ごとに一方的に下がる(=洪水の水位が上がり逃げ場が狭まる、という比喩)ため、下降率 $B$ が大きすぎると探索はすぐに行き詰まる。

入力形式

N S B T
A_1 A_2 ... A_N

制約

$2 \le N \le 1000$
$1 \le S \le N$
$0 \le B \le 10^4$
$1 \le T \le 1000$
$1 \le A_i \le 10^9$

入出力例

入力例1

10 1 1 5
9 7 8 4 5 2 6 3 9 1

出力例1

1
2 7

概念図:水位の下降と行き詰まり

10地点の高さと水位の推移 1 9 2 7 3 8 4 4 5 5 6 2 7 6 8 3 9 9 10 1 L=8 (it1) L=7 (it2) it1: pos1→pos2(隣接7≤8で移動、成功)。it2: pos2の隣接9,8とも7以下でなく実行可能な移動先なし→終了。

ヒント(段階的開示)

ヒント1(方向性)

焼きなまし法は「確率」で改悪を受理するかどうかを決めるが、大洪水法は完全に決定的な「水位」という1本の閾値だけで判定する。水位より高い場所には行けない、というルールだけを守って隣接地点を見ていけばよい。

ヒント2(アプローチ)

各反復で水位を先に下げてから、隣接候補のうち水位以下のものだけを候補にする。候補が無ければそこで探索は終わり。「水位を下げてから候補を絞る」順序を間違えないこと。

ヒント3(誘導)
pos = S
level = A[pos - 1]
executed = 0
for it in range(1, T + 1):
    level -= B
    cands = []
    if pos - 1 >= 1:
        cands.append(pos - 1)
    if pos + 1 <= N:
        cands.append(pos + 1)
    feasible = [c for c in cands if A[c - 1] <= level]
    if not feasible:
        break
    pos = min(feasible, key=lambda c: (A[c - 1], c))
    executed = it

模範解答 (Python)

import sys

def main():
    data = sys.stdin.read().split()
    ptr = 0
    n = int(data[ptr]); ptr += 1
    s = int(data[ptr]); ptr += 1
    b = int(data[ptr]); ptr += 1
    t = int(data[ptr]); ptr += 1
    a = [int(data[ptr + i]) for i in range(n)]
    ptr += n

    pos = s
    level = a[pos - 1]
    executed = 0
    for it in range(1, t + 1):
        level -= b
        cands = []
        if pos - 1 >= 1:
            cands.append(pos - 1)
        if pos + 1 <= n:
            cands.append(pos + 1)
        feasible = [c for c in cands if a[c - 1] <= level]
        if not feasible:
            break
        best = min(feasible, key=lambda c: (a[c - 1], c))
        pos = best
        executed = it

    print(executed)
    print(pos, a[pos - 1])

main()

Step-by-Step 解説

1反復1回目を追う
$L\gets 9-1=8$。pos=1の隣接候補は地点2のみ。$A_2=7\le8$なので移動、executed=1。
2反復2回目で行き詰まる
$L\gets8-1=7$。pos=2の隣接候補は地点1($A=9$)と地点3($A=8$)。どちらも7以下でなく実行可能な候補が無いので探索終了。
3出力を確定する
成功回数1、最終地点2、その高さ$A_2=7$。
4下降率0との対比
$B=0$なら水位は常に9のまま。地点2からは$A_1=9\le9$、$A_3=8\le9$がともに真になり探索は続行できる。下降率が探索の寿命を直接左右することが確認できる。

よくあるミス

ミス原因正しい書き方
水位を下げる前に候補を判定手順の順序を逆にする「水位を下げる→候補判定」の順を厳守
複数候補で先頭要素を採用minを使わない高さ最小・同点は地点番号最小の2段階タイブレーク
終了した反復もexecutedに含めるbreak前にカウント加算移動が成功した反復のみカウント
範囲外の地点を候補に入れる境界チェック漏れpos-1>=1とpos+1<=Nを確認
確率で改悪を受理してしまう焼きなまし法と混同大洪水法は完全に決定的な閾値判定のみ

次のステップ

  • 発展: 下降率$B$を可変にして、探索が$T$回すべて成功する最大の$B$を二分探索で求めてみる。
  • 次回予告: 蟻コロニー最適化(Ant Colony Optimization)

自己評価

自分の回答

気づき・メモ