問題
$N$ 頂点 $M$ 辺の無向グラフ $G$ が与えられる。
- $G$ が弦グラフ(Chordal Graph)かどうか判定せよ。
- 弦グラフであれば、最大クリークサイズ(クロマティック数に等しい)を求めよ。
- 弦グラフであれば、最小彩色数を出力せよ(弦グラフでは $\chi(G) = \omega(G)$)。
弦グラフ: 長さ 4 以上のすべての誘導閉路に弦(chord)がある(= すべての誘導閉路が三角形)グラフ。
入力形式
N M
u_1 v_1
...
u_M v_M
制約
$2 \leq N \leq 10^5$
$1 \leq M \leq 3 \times 10^5$
自己ループ・多重辺なし
入出力例
入力例 1
6 7
1 2
2 3
3 4
4 5
5 6
6 1
1 4
出力例 1
Yes
3
3
入力例 2
4 4
1 2
2 3
3 4
4 1
出力例 2
No
ヒント (段階的開示)
ヒント1: 方向性
弦グラフの判定には Perfect Elimination Ordering (PEO) を使う。PEO とは、頂点の順序 $v_1, v_2, \ldots, v_n$ で、各 $v_i$ が残りのグラフ($v_i, v_{i+1}, \ldots, v_n$)において単純化頂点(simplicial vertex)になっているもの。
ヒント2: アプローチ
PEO は Maximum Cardinality Search (MCS) で $O((N+M) \log N)$ で求められる。MCS: (1) 任意の頂点から開始、(2) 毎回「隣接済み頂点数が最大の未処理頂点」を選択、(3) これで得た逆順が PEO 候補。
ヒント3: 誘導
import heapq
from collections import defaultdict
def mcs(adj, n):
weight = [0] * (n+1)
visited = [False] * (n+1)
order = []
heap = [(-0, v) for v in range(1, n+1)]
heapq.heapify(heap)
while heap:
neg_w, v = heapq.heappop(heap)
if visited[v]: continue
visited[v] = True
order.append(v)
for u in adj[v]:
if not visited[u]:
weight[u] += 1
heapq.heappush(heap, (-weight[u], u))
return order[::-1] # PEO 候補(逆順)
模範解答 (Python)
import sys
from collections import defaultdict
import heapq
def solve():
data = sys.stdin.read().split()
pos = 0
N = int(data[pos]); pos += 1
M = int(data[pos]); pos += 1
adj = defaultdict(set)
for _ in range(M):
u = int(data[pos]); pos += 1
v = int(data[pos]); pos += 1
adj[u].add(v)
adj[v].add(u)
def mcs():
weight = defaultdict(int)
visited = set()
order = []
heap = [(0, v) for v in range(1, N+1)]
heapq.heapify(heap)
while heap:
neg_w, v = heapq.heappop(heap)
if v in visited:
continue
visited.add(v)
order.append(v)
for u in adj[v]:
if u not in visited:
weight[u] += 1
heapq.heappush(heap, (-weight[u], u))
return order[::-1]
peo = mcs()
pos_in_order = {v: i for i, v in enumerate(peo)}
is_chordal = True
max_clique = 1
for i, v in enumerate(peo):
later_neighbors = [u for u in adj[v] if pos_in_order[u] > i]
if not later_neighbors:
continue
earliest = min(later_neighbors, key=lambda u: pos_in_order[u])
earliest_later = set(u for u in adj[earliest] if pos_in_order[u] > pos_in_order[earliest])
for u in later_neighbors:
if u == earliest:
continue
if u not in adj[earliest]:
is_chordal = False
break
if not is_chordal:
break
clique_size = 1 + len(later_neighbors)
max_clique = max(max_clique, clique_size)
if not is_chordal:
print("No")
return
print("Yes")
print(max_clique)
# 弦グラフ: χ(G) = ω(G)
print(max_clique)
solve()
Step-by-Step 解説
1弦グラフの特徴づけ
弦グラフは以下と同値(Rose, Tarjan, Lueker 1976): PEO(完全消去順序)が存在する。ある頂点順序 $v_1, \ldots, v_n$ で各 $v_i$ が $G[\{v_i, \ldots, v_n\}]$ の simplicial vertex。Simplicial vertex: 近傍がクリークを形成する頂点。
弦グラフは以下と同値(Rose, Tarjan, Lueker 1976): PEO(完全消去順序)が存在する。ある頂点順序 $v_1, \ldots, v_n$ で各 $v_i$ が $G[\{v_i, \ldots, v_n\}]$ の simplicial vertex。Simplicial vertex: 近傍がクリークを形成する頂点。
2MCS アルゴリズム
各ステップで「すでに選ばれた頂点との隣接数が最大」の頂点を選ぶ。逆順が PEO 候補。計算量 $O((N+M) \log N)$。
各ステップで「すでに選ばれた頂点との隣接数が最大」の頂点を選ぶ。逆順が PEO 候補。計算量 $O((N+M) \log N)$。
3PEO の検証
MCS が出力した順序が本当に PEO かを確認: $v_i$ の後続隣接頂点の最前者 $w$ の後続隣接頂点が $v_i$ の後続隣接頂点を包含するか。
MCS が出力した順序が本当に PEO かを確認: $v_i$ の後続隣接頂点の最前者 $w$ の後続隣接頂点が $v_i$ の後続隣接頂点を包含するか。
4最大クリークと彩色数
弦グラフでは $\omega(G) = \chi(G)$(完全グラフ多面体定理)。MCS の過程で各ステップのクリークサイズの最大値が $\omega(G)$。
弦グラフでは $\omega(G) = \chi(G)$(完全グラフ多面体定理)。MCS の過程で各ステップのクリークサイズの最大値が $\omega(G)$。
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| MCS の逆順をPEO候補とするのを忘れる | 順番通りだと逆 | order[::-1] |
| クリークサイズを後続隣接のみで計算 | v 自身を含め忘れ | 1 + len(later_neighbors) |
| 非弦グラフでχ=ωと出力 | 定理の適用条件を忘れ | 弦グラフ判定後のみ適用 |
次のステップ
- 発展問題: ツリー幅(Treewidth)の計算(弦グラフと密接な関係)
- 応用: 弦グラフ上の最大独立集合(Greedy PEO 逆順)