D - タスクの実行順序 / Task Execution Order 解説 by admin
GPT 5.4 High概要
この問題は、タスク間の「先にやるべき」という依存関係を満たしつつ、実行順序を辞書順最小にする問題です。
グラフの トポロジカルソート を行い、毎回「今実行できるタスクの中で番号が最小のもの」を選べば解けます。
考察
各条件「タスク \(A_i\) をタスク \(B_i\) より先に行う」は、
有向辺 \(A_i \to B_i\) と考えることができます。
すると問題は、
- 有向グラフに対して
- すべての辺の向きを守るように頂点を並べる
- その中で辞書順最小の並びを求める
という形になります。
これはまさに トポロジカルソート の問題です。
重要な観察
ある時点で実行できるタスクは、
まだ終わっていないタスクのうち、前提条件をすべて満たしているもの です。
グラフで言うと、これは 入次数が \(0\) の頂点 に対応します。
なぜなら、そのタスクに入ってくる辺がない = 「まだ終わっていない前提タスクがない」からです。
辞書順最小にするには?
普通のトポロジカルソートでは、入次数 \(0\) の頂点をどれから取っても正しい順序になります。
しかし今回は 辞書順最小 にしたいので、
- 実行可能なタスクが複数あるなら
- 番号が最小のものを必ず選ぶ
必要があります。
この操作を効率よく行うために、入次数 \(0\) の頂点を 最小ヒープ で管理します。
素朴な方法がなぜだめか
たとえば毎回
- 全タスクを見て入次数 \(0\) のものを探す
- その中で最小番号を選ぶ
とすると、各ステップで \(O(N)\) かかり、全体で \(O(N^2)\) になります。
\(N \leq 2 \times 10^5\) なので、これは間に合いません。
そこで、
- 入次数を配列で管理する
- 入次数が \(0\) になったタスクをヒープに入れる
- ヒープから最小番号を取り出す
という形にすると、高速に処理できます。
実行不可能な場合
前提条件に矛盾があると、グラフの中に 閉路 ができます。
例: - \(1 \to 2\) - \(2 \to 3\) - \(3 \to 1\)
この場合、どのタスクも誰かを先にやる必要があり、最初に始められるタスクがありません。
トポロジカルソートでは、最終的に並べられた頂点数が \(N\) 未満になります。
そのときは -1 を出力すればよいです。
アルゴリズム
以下の手順で求めます。
グラフを隣接リストで構築する
- 辺 \(A_i \to B_i\) を追加
- 同時に \(B_i\) の入次数を \(1\) 増やす
入次数が \(0\) のタスクをすべて最小ヒープに入れる
ヒープが空になるまで以下を繰り返す
- ヒープから最小のタスク番号 \(v\) を取り出す
- 答えに追加する
- \(v\) から出る辺 \(v \to nv\) について
- \(nv\) の入次数を \(1\) 減らす
- 入次数が \(0\) になったらヒープに入れる
最後に、答えに入ったタスク数を確認する
- \(N\) 個ならその順序を出力
- \(N\) 個未満なら閉路があるので
-1
具体例
例えば
- \(1 \to 3\)
- \(2 \to 3\)
のとき、最初にできるのは \(1, 2\) です。
辞書順最小にするには、まず \(1\) を選びます。
次にまだ \(2\) が選べるので \(2\) を選び、最後に \(3\) が選べます。
よって答えは
\(1, 2, 3\)
になります。
もしヒープを使わず適当に選ぶと \(2,1,3\) になることもありますが、これは辞書順最小ではありません。
計算量
- 時間計算量: \(O((N + M)\log N)\)
- 空間計算量: \(O(N + M)\)
実装のポイント
- グラフは
adj[a].append(b)の形で隣接リストにする - 入次数は
indeg[b] += 1で管理する - 辞書順最小にするため、入次数 \(0\) の頂点集合は キューではなく最小ヒープ を使う
- 最後に
len(order) != nなら、閉路があって実行不可能なので-1を出力する
このコードはまさにその方針をそのまま実装したものです。
ソースコード
import sys
import heapq
def main():
data = list(map(int, sys.stdin.buffer.read().split()))
if not data:
return
n, m = data[0], data[1]
adj = [[] for _ in range(n + 1)]
indeg = [0] * (n + 1)
idx = 2
for _ in range(m):
a = data[idx]
b = data[idx + 1]
idx += 2
adj[a].append(b)
indeg[b] += 1
h = []
for i in range(1, n + 1):
if indeg[i] == 0:
heapq.heappush(h, i)
order = []
while h:
v = heapq.heappop(h)
order.append(v)
for nv in adj[v]:
indeg[nv] -= 1
if indeg[nv] == 0:
heapq.heappush(h, nv)
if len(order) != n:
print(-1)
else:
print(*order)
if __name__ == "__main__":
main()
この解説は gpt-5.4-high によって生成されました。