問題
$N$ 個の非負整数からなる数列 $d_1,\dots,d_N$ が与えられる。この数列を次数列として持つ単純無向グラフ(自己ループ・多重辺なし、頂点数 $N$)が存在するかどうかを判定せよ。存在するなら Yes、存在しないなら No を出力せよ。
制約
| パラメータ | 範囲 | 備考 |
|---|---|---|
| $N$ | $1 \le N \le 2000$ | 数列の長さ=頂点数 |
| $d_i$ | $0 \le d_i \le N-1$ | 各頂点の次数 |
入出力例
入力例1
6
3 3 2 2 1 1
出力例1
Yes
入力例2
4
3 3 3 1
出力例2
No
例2は次数合計10(偶数)で必要条件は満たすが、Havel-Hakimi法で構成すると途中で負の次数が生じ実現不可能。
概念図: 最大次数の頂点を、次数が大きい順に接続する貪欲構成
ヒント
ヒント1(方向性)
次数列がグラフとして実現可能かどうかを判定する古典的な定理が2つある:Erdős–Gallai の定理(不等式による判定)と Havel-Hakimi 法(構成的アルゴリズム)。ここでは後者を実装する。
ヒント2(アプローチ)
次数の合計が奇数なら不可能(握手補題)。合計が偶数の場合、「最大次数の頂点は、次数が大きい頂点から順に辺を張るのが最適」という貪欲法が成り立つ。最大次数 $k$ の頂点を数列から除去し、残りのうち次数が大きい上位 $k$ 個を1ずつ減らす、を繰り返す。
ヒント3(ほぼ答え)
def is_graphical(d):
if sum(d) % 2 != 0:
return False
while True:
d.sort(reverse=True)
if d[0] == 0:
return True
k = d[0]
d = d[1:]
if k > len(d):
return False
for i in range(k):
d[i] -= 1
if d[i] < 0:
return False
模範解答
import sys
def main():
data = sys.stdin.read().split()
n = int(data[0])
d = list(map(int, data[1:1 + n]))
if sum(d) % 2 != 0 or any(x < 0 or x > n - 1 for x in d):
print("No")
return
while True:
d.sort(reverse=True)
if d[0] == 0:
print("Yes")
return
k = d[0]
d = d[1:]
if k > len(d):
print("No")
return
for i in range(k):
d[i] -= 1
if d[i] < 0:
print("No")
return
main()
計算量: 各イテレーションで要素数が1減り、ソートに $O(N\log N)$ かかるため全体で $O(N^2\log N)$。
Step-by-Step 解説
Step 1: 必要条件のチェック
次数合計は必ず偶数(握手補題)。次数は $N-1$ を超えられない。これらを満たさなければ即座に No。
Step 2: 最大次数の頂点を選び貪欲に接続
降順ソートし先頭値 $k$ を取り出す。残りの上位 $k$ 個に接続するのが最適(Havel-Hakimi の定理)。
Step 3: 接続先の次数を1ずつ減らす
上位 $k$ 個を1ずつ減らし、負になれば矛盾で No。
Step 4: 繰り返して全次数が0になるか判定
全て0になれば Yes。k > 残り頂点数 となれば No。
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| 次数合計の偶奇チェックを忘れる | 握手補題の見落とし | ループ前に sum(d)%2!=0 を確認 |
k > len(d) のチェック漏れで例外 | 残り頂点数より大きい次数を想定していない | 減算前に範囲チェックを入れる |
| 次数が負になっても検出せず誤判定 | 負の次数を許してしまう | 減算のたびに<0をチェック |
次のステップ
- 発展問題: Erdős–Gallai の定理を使った高速判定への置き換え
- 発展問題: 実現可能な場合の辺集合の具体的な構成・出力
自己評価
理解度: / /
自分の回答:
気づき・メモ: