Pythonで同時に成立する部屋移動リクエストの最大数を求めるプログラム
寮には0からn-1までの番号が付けられたn個の部屋があるとします。各部屋の学生は別の部屋へ引っ越したいと考えており、そのために複数の移動リクエストを提出します。ただし、寮の席が空いたままになることは許されず、移動を希望する学生の代わりに別の学生がその部屋に入ってくれる場合にのみ、移動リクエストが受理されます。そこで本記事では、与えられたリクエストの中から、実際に同時に満たすことのできるリクエストの最大数を求める方法を解説します。
例えば、入力が n = 3、requests = [[0,2],[1,0],[2,1]] の場合、出力は 3 になります。これは次のように3件すべての移動が成立するためです。
- 部屋0の学生が部屋2へ移動する
- 部屋1の学生が部屋0へ移動する
- 部屋2の学生が部屋1へ移動する
解決のアプローチ
この問題は、リクエストの部分集合をすべて試す「組み合わせ全探索」で解くことができます。ある集合のリクエストがすべて同時に成立するためには、各部屋について「出ていく人数」と「入ってくる人数」が一致していなければなりません。具体的な手順は以下の通りです。
- k をリクエスト総数から1まで1ずつ減らしながらループする
- リクエストの中から k 個を選ぶすべての組み合わせについて調べる
- サイズ n の配列 d を用意し、すべての要素を0で初期化する
- 選ばれた各リクエスト i について、出発元の部屋のカウントを1減らし、移動先の部屋のカウントを1増やす
- d のすべての要素が0(空席も過剰な入居も発生していない状態)であれば、その k が答えなので返す
どの組み合わせでも条件を満たせなかった場合は 0 を返します。計算量はリクエスト数を m とすると O(2m × m) となり指数オーダーですが、リクエスト数が少ない場合は十分実用的です。
実装例
理解を深めるために、Pythonでの実装を見てみましょう。
from itertools import combinations
def solve(n, requests):
for k in range(len(requests), 0, -1):
for c in combinations(range(len(requests)), k):
d = [0] * n
for i in c:
d[requests[i][0]] -= 1
d[requests[i][1]] += 1
if not any(d):
return k
return 0
print(solve(3, [[0,2],[1,0],[2,1]]))
入力
3, [[0,2],[1,0],[2,1]]
出力
3
このコードでは、itertools.combinations を使って選択するリクエストの組み合わせを列挙し、大きい k から順に検証することで、最初に見つかった成立する組み合わせのサイズが最大値になります。配列 d の要素がすべて0であることを any 関数の否定で判定している点がポイントです。
-
【Python】N×N行列の空セル選択パターン数を数えるプログラムの書き方
問題概要 N × N の2値行列を考えます。ここで 0 は空のセル、1 はブロックされたセルを表します。このとき、「すべての行とすべての列に、選ばれたセルが少なくとも1つ含まれる」ように N 個の空のセルを選ぶ方法の数を求めます。答えが非常に大きくなる可能性があるため、結果は 10^9 + 7 で割った余りとして返します。 例えば、入力が次のような行列だったとします。 000000010 この場合、出力は 4 になります。以下の4通りの配置(x が選択されたセルを表す)が存在するためです。 アプローチ:ビットマスクを使った再帰探索 この問題は、行ごとに順番に処理を進めていく再帰的な探索で解
-
Pythonで捕まえられる雨水の総量を計算するプログラム(トレッピング・レイン・ウォーター問題)
非負整数からなる長さ n の配列が与えられているとします。各要素はバーの高さを表し、それぞれのバーの幅は1です。このとき、雨が降った後に溜め込むことのできる水の総量を計算するのが本記事のテーマです。状況を図にすると、以下のようになります。図を見ると、水が溜まっている部分(青い箱)は全部で8個あります。したがって、このケースの出力は8となります。解法のアプローチこの問題は「スタック」を利用することで効率よく解けます。全体の手順は以下の通りです。スタック st、変数 water := 0、インデックス i := 0 を用意するi が高さ配列のサイズ未満である間、次の処理を繰り返すスタックが空である