AtCoder 茶~緑向け:貪欲法の合法性を証明するための実践的ガイド
はじめに
「貪欲法で解けることを証明したか」という問いに自信を持って答えるのは、実際に問題を解くよりも難易度が高いです。少なくとも、現時点で茶~緑ランクの私には難しく感じます。
なぜ貪欲法の証明が必要で、具体的に何を押さえれば「証明した」と言えるのでしょうか?
本記事では、貪欲法に対する”直観”を”最低限の論理”に変換する力を身につけること、実装前に自信を持つためのチェックリストを獲得することを目標とし、ABC431 C 問題を例に「競プロにおける証明の立て方」を考察します。
貪欲法の証明はなぜ難しいのか
前提として、なぜ貪欲法の証明が必要なのでしょうか。それは貪欲法が「局所的に最善の選択をする」という手法であり、このアルゴリズムが「全体で最善の選択になるか」が保証されないからです。
読んでくださっている皆さんにも「なんとなくいけそう」という思考で提出して WA を食らったり、提出を躊躇して時間を取られたりした経験があるかもしれません。自信を持って証明ができないと、本番のスコアに影響してしまいます。
正しさの裏付けができなかったり、自信を失ってしまったりするのは「証明のパターン」を知らないのが原因かもしれません。
貪欲法の証明のパターン
貪欲法の証明は多様に見えますが、実際には以下に示す 3 つのパターンのいずれか(またはその組み合わせ)でほとんど説明できます。
以下の章で、それぞれのパターンについて証明手順を示します。
まずは全パターンに共通する点を挙げておきます。
本記事では「与えられた選択肢から 1 つを選ぶことを順に繰り返したときの選び方」を解と呼ぶこととします。
いずれのパターンにせよ、まずは貪欲法によってどんな解
問題によって
- 解が存在するかを問われていれば「貪欲法によって解
が得られる ⇒ 解が存在する」ことG - 最適解が求められているなら「最適解が
である ⇒ 解X が最適解G に含まれる」ことX
をそれぞれ証明すればよいです。
各証明パターンは数学的に重なる部分がありますが、問題によって適したアプローチを選べると楽に証明できます。
交換論法:順番を変えても損をしない
昇順や降順に選ぶのが最適であると主張したいとき、
- 昇順/降順から逆転している箇所を交換したとき、全体のスコア(目的関数)が悪くならないこと
を示すことで、任意の最適解
- 最適解
の中に貪欲法O の順序と逆転する箇所があると仮定する。G - その部分を
と交換した解G を作る。O' - この交換が
- ほかの要素の選択を妨げることがなく
- 全体のスコアを悪くしないこと
を示す。このとき、解 も最適解である。O'
- 逆転がなくなるまでこれを繰り返すことにより、最終的に
をO と同じ順序に変形できる。G
要素の順序だけが重要な問題で使いやすいパターンです。
追い越し不能:常に有利な状況を保てる
目先の利益を常に最大化すれば全体を最大化できると主張したいとき、
- 貪欲に
個選んだ状態とほかの選び方とを比べたとき、前者のほうが「後に続く選択肢が広い」状態にあるかi
を示すことで、貪欲な選択
- 任意の解
と貪欲な選択X を比べて、初めのG 回の選択をした後に残りの選択肢が等しいか、またはi - 1 のほうが広いと仮定する。G -
回目の選択において、貪欲な選択をしても残りの選択肢が狭まらない(単調性が保たれる)ことを示す。i - 数学的帰納法より、
は常にほかの解よりも不利にならず、結果として最適解の 1 つである。G
区間スケジュール問題などで使いやすいパターンです。
縮小法:一部を決めても全体最適が崩れない
部分問題において貪欲な選択が最適であり、これを繰り返して全体の最適解が得られると主張したいとき、
- 先頭で貪欲な選択を行った後の残りの問題が「サイズが小さくなっただけの同じ問題」となること
を示すことで、貪欲な選択
- 最初の選択として、貪欲に最善の要素を選ぶ。
- この選択によって最適解に到達できなくなることがないことを示す。
- すなわち、最適解のうち少なくとも 1 つはこの選択を先頭に含むことを示せばよいです。
- 最初の選択をした後、残りの部分問題が(サイズが小さい)同じ構造の問題となっていることを示す。
- 数学的帰納法より、貪欲な選択を繰り返すことで全体の最適解
が得られる。G
一般の最適化問題で、部分問題での選択が全体の問題の性質を変えないときに使えるパターンです。
実例:ペア形成問題の貪欲法による解法と証明
以下では ABC 431 C - Robot Factory を例に、直観的な理解と「縮小法」を用いた証明手順を示します。
問題の概要
- 数列
およびH が与えられる。数列B の長さはH で、数列N の長さはB である。M - 数列
の要素H, B を取り出してH_i,\ B_i となるようなペアH_i \le B_i を(H_i, B_i) 個作りたい。K - 与えられた
について、実際にK \le min(N, M) 個のペアを作れるか否かを答えよ。K - ただし複数のペアで 1 つの要素を共有してはならない。
直観的な理解
ペアを 1 つ作る際、「数列
- ペアを作る際に数列
の小さな要素を選ぶほど、相手に選べるH の要素の候補が増えるB - 逆に数列
の大きな要素を選ぶほど、相手に選べるH の要素の候補が減るB
この直観から、貪欲法が使えそうだと考えられます。
実装
まず、貪欲法による
- 数列
を昇順H に並べ、小さいほうから(h_1,\ \dots,\ h_N) 個を取り出して数列K を作る。H' = (h_1, ..., h_K) -
の側はできるだけ前の要素に小さい値を割り当てたいので、最小のH 個を選んでいます。K
-
- 数列
を昇順B に並べ、大きいほうから(b_1,\ \dots,\ b_M) 個を取り出して数列K を作る。B' = (b_{M - K + 1},\ \dots,\ b_M) -
の側はできるだけ後ろの要素に大きい値を割り当てたいので、最大のB 個を選んでいます。K
-
- ペア
を作る(以下、簡単のため(h_1, b_{M - K + i}) と表す)。(H'_i, B'_i) - もし 1 組でも
となる場合、H'_i > B'_i 個のペアは作れない。K - 各ペアについて
が成り立つならばH'_i \le B'_i 個のペアを作れる。K
上記の解法を python で実装した部分コードを示します。
H.sort()
B.sort()
Hd = H[:K]
Bd = B[-K:]
for i in range(K):
if not (Hd[i] <= Bd[i]):
ans = "No"
break
else:
ans = "Yes"
縮小法による証明
ここでは「縮小法:一部を決めても全体最適が崩れない」パターンに則って貪欲法による解が最適であることを示します。
証明したいこと
ここで証明したいのは以下の事柄です。
- 必要条件:
個のペアを作れるK すべての\implies に対して1 \le i \le K が成り立つH'_i \le B'_i - 十分条件:
個のペアを作れるK すべての\impliedby に対して1 \le i \le K が成り立つH'_i \le B'_i
十分条件については、もしすべての
よって、以下で必要条件のほうを証明します。
0. 証明のための準備
証明の前準備として、次の補題が成り立つことを示します。
-
から任意にH 個を取って昇順に並べた数列をK とする。S = (s_1 \le \cdots \le s_K) - これと
から小さい順にH 個を取り出した数列K について以下のことが成り立つ。H' -
に対して1 \le i \le K h_i \le s_i
-
- 同様に、
から任意にB 個を取って昇順に並べた数列をK とする。T = (t_1 \le \cdots \le t_K) - これと
から大きい順にB 個を取り出して昇順に並べた数列K について以下のことが成り立つ。B' -
に対して1 \le i \le K t_i \le b_{M - K + i}
-
1. 最初に貪欲な最善手を取る
- 先ほど定義した
でS,\ T 個のペアが作れると仮定します。ここでK はs_1 の中で最小の要素です。S - 一方で全体の集合
の最小の要素をH とすると、明らかにh_1 が成り立ちます。h_1 \le s_1
2. 選択によって最適解に到達できなくなることがない
-
が成り立つため、ペアh_1 \le s_1 \le t_1 は制約を満たします。(h_1, t_1) -
の最小要素S は、s_1 全体からH が選んだS 個のうち最小の要素です。一方でK はh_1 全体の最小値なので、H がh_1 に含まれる場合、必ずS になります。h_1 = s_1 -
の場合、s_1 \ne h_1 にS は含まれません。h_1 - よって
で作ったS,\ T 個のペアのうち先頭をK に置き換えても、残り(h_1, t_1) 個のペアの可否は変わりません。K - 1
3. 残りの部分問題が同じ構造の問題となっている
- 先頭のペアを
と固定すると、残りの部分問題は以下のように表せます。(h_1, t_1) -
、H \setminus \{h_1\} からB \setminus \{t_1\} 個のペアを作る。K - 1 - ここで
はH \setminus \{h_1\} からH を取り除いた集合を意味します。h_1
-
- この問題は確かに元の問題と同じ構造を持っています。
4. 同じ操作を繰り返すことで全体の最適解が得られる
- 同様の手順で、すべてのペア
を(s_i, t_i) に置き換えることができます。(h_i, t_i)
はT 全体から任意に選ばれたB 個、K は貪欲に選んだB' のうち最大のB 個なので、すべてのK についてt_i が成り立ちます。h_i \le t_i \le b_{M - K + i} - 以上より、仮定「
でS,\ T 個のペアが作れること」から「K についてi = 1,\ \dots,\ K が成り立つこと」を導くことができました。H'_i \le B'_i
貪欲法チェックリスト
以下のポイントをチェックすると、貪欲法が使えるかどうかを直観的に判断するのに役立ちます。
- 後戻りは不要か?
- 本当に有利なものから取ってよいか?
- 交換しても損しないか?
- 部分問題に分解できるか?
1. 後戻りは不要か?
ある時点で選んだ選択肢が将来の選択肢を消滅させたり、後が続かなくなるような悪影響を与えたりする場合、貪欲法は使えない可能性が高いです。
2. 本当に有利なものから取ってよいか?
ナップサック問題のように最大値/最小値以外の値を取ることで得するケースが考えられる場合、貪欲法は使えません。
3. 交換しても損しないか?
ある最適解を仮定したとき、その一部を貪欲な選択と入れ替えても解が悪化しないと言い切れる場合は貪欲法を使える場合があります。
4. 部分問題に分解できるか?
「1 つ選んだ後の残りの問題」が元の問題と同じ構造をしている(サイズが
他の問題への応用
- 区間スケジューリング問題(終端ソート問題)
- 終わるのが早いものを選ぶことにより残りの時間が最大化される、単調性があるパターンの代表例です。
- 辞書順最小
- 前の文字を辞書順に小さくすることのほうが、後ろの文字の順序よりも優先される例です。
- 最小全域木(クラスカル法)
- コストの小さい辺から採用する貪欲法が正当である有名な例です。
終わりに
解法が正しいかどうかの不安や曖昧さは「証明のパターン」と「パターンへの落とし込み方」がわかれば解消できます。
本番中の判断ではあくまで最低限の論理が押さえられていれば問題ありませんので、ぜひ復習の際には上記のパターンを活用して証明してみてください。
本記事が、貪欲法を自信を持って使えるようになるまでの一助になれば幸いです。
Discussion