接種枠を埋める問題
新型コロナワクチンWeb予約抽選申込フォームについてという記事に、説明用として次のような例が挙げてある。
6人の接種希望対象者A〜Fがいる。各対象者は、会場・時間帯の異なる枠1〜10のうち、希望する枠をいくつでも選べる(元記事では会場1〜5の午前・午後であったが、ここでは1〜10の通し番号とした)。1つの枠には1人しか入れない。元記事では、対象者にランダムに順位を付け、その順に枠を左から埋めていく。
| 対象者 | 順位 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| A | 3 | ○ | ○ | ○ | ○ | ○ | |||||
| B | 4 | ○ | ○ | ○ | ○ | ○ | ○ | ○ | ○ | ○ | ○ |
| C | 6 | ○ | ○ | ○ | ○ | ○ | ○ | ○ | |||
| D | 5 | ○ | ○ | ||||||||
| E | 1 | ○ | |||||||||
| F | 2 | ○ | ○ |
しかし、実際にやってみると、うまく全員が埋まらない。埋まった枠を●で表すと、次のようになる:
| 対象者 | 順位 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| A | 3 | ● | ○ | ○ | ○ | ○ | |||||
| B | 4 | ○ | ● | ○ | ○ | ○ | ○ | ○ | ○ | ○ | ○ |
| C | 6 | ○ | ● | ○ | ○ | ○ | ○ | ○ | |||
| D | 5 | ○ | ○ | ||||||||
| E | 1 | ● | |||||||||
| F | 2 | ● | ○ |
順位5のDさんは枠1・6を希望したが、枠1は順位3のAさんが先に取り、枠6は順位2のFさんが先に取ってしまっているので、どこにも入れない。
しかし、AさんかDさんに別の枠に移ってもらえば、うまく全員が収まる。
このような場合にできるだけ多くの人に枠を割り振る問題は、2部グラフの最大マッチング(maximum bipartite matching)の問題と呼ばれる。
深さ優先探索による簡単な解き方(Maximum Bipartite Matching 参照):
slots = {
'A': [1, 3, 5, 7, 9],
'B': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
'C': [2, 4, 5, 6, 7, 8, 9],
'D': [1, 6],
'E': [3],
'F': [6, 8]
}
order = {
'A': 3,
'B': 4,
'C': 6,
'D': 5,
'E': 1,
'F': 2
}
applicants = {} # empty dict
def bpm(applicant, visited):
for slot in slots[applicant]:
if slot not in visited:
visited.add(slot)
if slot not in applicants or bpm(applicants[slot], visited):
applicants[slot] = applicant
return True
return False
results = 0
for applicant in sorted(slots.keys(), key=lambda k: order[k]):
visited = set() # empty set
if bpm(applicant, visited):
results += 1
print(f"埋まった人数: {results}")
print("枠と対象者:")
for slot in sorted(applicants):
print(f"{slot}: {applicants[slot]}")
for applicant in ... のループは、実際にはどういう順序で埋めていっても埋まる人数は同じなので、単に
for applicant in slots.keys():
でもよいが、全員が埋まらない場合はループの順序に依存するので、order の昇順に埋めていく。あるいは、順位をランダムに定めるよりは、たくさんの枠を書いてくれた人を優先するために
for applicant in sorted(slots.keys(), key=lambda k: len(slots[k]), reverse=True):
のようにするのでもよいかもしれない。