Pythonで半分全列挙を実装してみる-ABC184
本記事では、半分全列挙をPythonで実装していきます。
実際に一緒に問題を解きながら半分全列挙を理解しましょう。
半分全列挙とは
半分全列挙は、その名の通り個の要素を半分に分けてそれぞれを全列挙し、半分に分けたグループから組み合わせを考えることで高速に解を求める方法です。
具体的には以下のような流れになることが多いです。
- 半分のグループを全列挙
- もう半分のグループを全列挙
- から要素を1つ選び固定し、二分探索を用いて最適な組み合わせを探す(全ての要素に対して繰り返す)
それでは、実際に例題を解きながら理解していきましょう。
例題
今回は以下の問題を扱います。
atcoder.jp
問題文
高橋くんはプログラミングコンテストに参加します。 このコンテストのコンテスト時間は 分間で、 問の問題が出題されます。高橋くんは超能力者なので、 番目の問題が 分で解けることが分かっています。高橋くんは 問の中から 0 問以上を、解くのにかかる時間の総和が 分以下になるように選び、それらの問題を解きます。
選んだ問題を解くのにかかる時間の総和の最大値を求めてください。
制約
- 入力は全て整数
実装
選んだ問題を「解く」「解かない」の2パターンで考えることができるので全探索で答えを求めることはできます。
しかし、この例題における全探索の計算量はです。また、なので、到底全探索では間に合いません。
そこで、半分だけ全列挙した場合の計算量を考えてみましょう。半分であれば最大でもなので程度で計算することが可能です。
半分全列挙した前半のグループを、後半のグループをと呼ぶことにします。
半分に全列挙することができれば、あとは皆さんご存知の二分探索で解を求めることができます。
例えば、の要素であるを固定した場合、「という条件を満たす最大のを探す」という話になります。
の要素を固定して全探索するにはかかり、二分探索にはかかるので、全体ででこの問題を解くことができます。
以下が実装例のコードです。
from bisect import bisect_right N, T = map(int, input().split()) A = list(map(int, input().split())) L = [0] R = [0] for x in A[:20]: for i in range(len(L)): L.append(x + L[i]) for x in A[20:]: for i in range(len(R)): R.append(x + R[i]) R.sort() ans = 0 for x in L: if x > T: continue y = R[bisect_right(R, T - x) - 1] if ans < x + y: ans = x + y print(ans)
まとめ
半分全列挙はさほど難しいアルゴリズムではないですね。
正しく列挙し、二分探索を行うことができれば解ける問題でした。
の数が少ない場合などに半分全列挙の可能性を考えてみてもいいかもしれません。
おわり