Algorithm | 優先度付きキュー（heapq）をPython3で解説（例題あり）

## 優先度付きキューとは

優先度付きキュー（priority queue）とは、優先度にしたがって、優先度の高いものから順番に取り出すことができるものだ。

その代表例が**ヒープキュー（heapq）**である。公式ドキュメントはこちら。（https://docs.python.org/ja/3/library/heapq.html）

特徴は大きく分けて2つある。

- 最小値、最大値を素早く取り出すことができる
- 値の挿入順は気にしなくても良い

主に使うメソッドは次のとおりだ。

- ```heapq.heapify(heap, item)```：優先度付きキューの作成
- ```heapq.heappop(heap)```：最小値の取り出し
- ```heapq.heapush(heap, item)```：キューへの挿入

```heapq.heappop(heap)```、```heapq.heapush(heap, item)```の計算量はどちらも$O(logN)$なので、通常のリストでの操作よりも少ない計算量で実装できる。

```python
# heapq
import heapq

numbers = [3, 5, 1, 6, 2, 4]

heapq.heapify(numbers) # 優先度付きキューの作成

print(numbers)

print(heapq.heappop(numbers)) # 最小値の取り出し
print(heapq.heappop(numbers))

print(numbers)

heapq.heappush(numbers, 1) # キューへの挿入
heapq.heappush(numbers, 2)

print(numbers)

print(heapq.heappop(numbers)) # 最小値の取り出し
print(heapq.heappop(numbers))

print(numbers)
```

```python
>> [1, 2, 3, 6, 5, 4]
>> 1
>> 2
>> [3, 4, 5, 6]
>> [1, 3, 2, 6, 4, 5]
>> 1
>> 2
>> [3, 4, 5, 6]
```

## 例題1: ABC137 D - Summer Vacation
https://atcoder.jp/contests/abc137/tasks/abc137_d

```python
def main():
    import heapq

    n, m = map(int, input().split())

    jobs = [[] for _ in range(m+1)]

    for _ in range(n): # 各日のリストに各仕事の報酬を追加
        a, b = map(int, input().split())

        if a <= m:
            jobs[a].append(b)

    heap = []
    heapq.heapify(heap)
    total = 0

    for day in range(1, m+1):
        for i in jobs[day]:
            heapq.heappush(heap, -i) # 最大値を取れるように-iでpush

        if len(heap) > 0: # もし値があれば、
            total += abs(heapq.heappop(heap)) # 最大値を取得

    print(total)

if __name__ == '__main__':
    main()
```

## 例題2: ABC141 D - Powerful Discount Tickets
https://atcoder.jp/contests/abc141/tasks/abc141_d

```python
def main():
    import heapq

    n, m = map(int, input().split())
    alist = list(map(lambda x: int(x)*(-1), input().split()))

    heapq.heapify(alist)

    for _ in range(m):
        temp = heapq.heappop(alist)*(-1)//2
        heapq.heappush(alist, temp*(-1))

    print(sum(map(lambda x: -x, alist)))


if __name__ == '__main__':
    main()
```

## 例題3: ABC212 D - Querying Multiset
https://atcoder.jp/contests/abc212/tasks/abc212_d

```python
def main():
    import heapq

    q = int(input())
    sack = []
    heapq.heapify(sack)
    total = 0

    for i in range(q):
        query = list(map(int, input().split()))
        if query[0] == 1:
            heapq.heappush(sack, query[1]-total)
        elif query[0] == 2:
            total += query[1]
        else:
            ans = heapq.heappop(sack)
            print(ans+total)

if __name__ == '__main__':
    main()
```

## 例題4: 第二回アルゴリズム検定 F - タスクの消化
https://atcoder.jp/contests/past202004-open/tasks/past202004_f

```python
def main():
    import heapq

    n = int(input())

    tasks = [[] for _ in range(n+1)]

    for _ in range(n):
        a, b = map(int, input().split())

        tasks[a].append(b)

    heap = []
    heapq.heapify(heap)
    total = 0

    for day in range(1, n+1):
        for i in tasks[day]:
            heapq.heappush(heap, -i)  # 最大値の取得

        if len(heap) > 0:
            total += abs(heapq.heappop(heap))  # 最大値を加算

        print(total)


if __name__ == '__main__':
    main()
```

## 例題5: ABC173 D - Chat in a Circle
https://atcoder.jp/contests/abc173/tasks/abc173_d

```python
def main():
    import heapq

    n = int(input())
    alist = list(map(int, input().split()))

    alist.sort(reverse=True)

    total = 0
    heap = []

    heapq.heappush(heap, alist[0]*(-1)) # 最初に到着した人

    for i in range(1, n):
        friendly = heapq.heappop(heap)*(-1) # 輪の中で最大値を取る
        total += friendly # 心地よさに加算する
        heapq.heappush(heap, alist[i]*(-1)) # 輪に次の人を入れる
        heapq.heappush(heap, alist[i]*(-1))

    print(total)


if __name__ == '__main__':
    main()
```

## 例題6: ABC217 E - Sorting Queries

https://atcoder.jp/contests/abc217/tasks/abc217_e

```python
def main():
    from collections import deque
    import heapq

    q = int(input())

    que = deque()
    heap = []

    for _ in range(q):
        query = list(map(int, input().split()))
        if query[0] == 1:
            que.append(query[1]) # queに値を追加
        elif query[0] == 2:
            if len(heap) > 0: # heapに値が存在したら
                print(heapq.heappop(heap)) # heapの最小値を取り出す
            else: # heapに値がなかったら
                print(que.popleft()) # queから値を取り出す
        else:
            while que: # queの中身をすべてソートながらheapに挿入
                heapq.heappush(heap, que.pop())


if __name__ == '__main__':
    main()
```

## 例題7: ABC223 D - Restricted Permutation

https://atcoder.jp/contests/abc223/tasks/abc223_d

heapqを用いたトポロジカルソート。

```python
def main():
    from heapq import heappush, heappop

    n, m = map(int, input().split())
    indeg = [0] * (n+1)  # 入次数
    out = [[] for _ in range(n+1)]

    for _ in range(m):
        a, b = map(int, input().split())
        indeg[b] += 1
        out[a].append(b)

    heap = []

    # 入次数が0の数字を探す
    for i in range(1, n+1):
        # 入次数が0ならば
        if indeg[i] == 0:
            heappush(heap, i)

    ans = []

    while heap:  # すべての数字を見るまで続行
        # 辞書順で最小
        # つまり、数字が小さいものから取り出す
        now = heappop(heap)
        ans.append(now)
        # 次の数字はどこへ向かうか
        for to in out[now]:
            indeg[to] -= 1
            # 入次数が0の数字をheapに追加
            if indeg[to] == 0:
                heappush(heap, to)

    if len(ans) == n:
        print(*ans)
    else:
        print(-1)


if __name__ == '__main__':
    main()
```

## まとめ

```heapq``` は実装こそ難しくないので、使い慣れると最大の武器となる。
問題に行き詰まったら、heapが使えないか確認する癖をつけておこう。