Branched Evolution

Competitive Programming in Python

ABC 187 F - Close Group

無向グラフについて、頂点を共有しない完全部分グラフの最小個数を求める。

F - Close Group

解説

頂点からなる集合$S$の完全部分グラフの最小個数を$dp[S]$とする。$dp[\phi]=0$で、$S$が完全グラフをなすならば$dp[S]=1$である。それ以外の場合、 $$ dp[S]=\min \{ dp[T] + dp[S\setminus T] \mid T \subset S \} $$ となる。

実装

グラフは隣接行列で管理した。

$\{0,\ldots, n - 1 \}$の部分集合$S$に対して、$S$の空でない部分集合を列挙するには次のようにすればよい。例えば、$S=\{0,1,3\}$の場合、$11 = 2 ^ 0 + 2 ^ 1 + 2 ^ 3$だから

s = 11
t = s
while t:
    print(t)
    t = (t - 1) & s
# 11, 10, 9, 8, 3, 2, 1

Submission #19170982 - AtCoder Beginner Contest 187

n, m = map(int, input().split())
graph = [[0] * n for _ in range(n)]
for _ in range(m):
    a, b = map(int, input().split())
    graph[a - 1][b - 1] = 1
    graph[b - 1][a - 1] = 1
dp = [n] * (1 << n)
dp[0] = 0
bit_count = [bin(s).count("1") for s in range(1 << n)]
for s in range(1, 1 << n):
    # 1点の場合
    if bit_count[s] == 1:
        dp[s] = 1
        continue
    # 完全部分グラフの場合
    comp = True
    for i in range(n - 1):
        if not (1 << i) & s:
            continue
        for j in range(i + 1, n):
            if not (1 << j) & s:
                continue
            if not graph[i][j]:
                comp = False
                break
    if comp:
        dp[s] = 1
        continue
    # その他
    t = s
    while t:
        t = (t - 1) & s
        dp[s] = min(dp[s], dp[t] + dp[s - t])
print(dp[(1 << n) - 1])

ちなみに、隣接行列ではなく各頂点に隣接する点の集合を管理するようにすると、完全部分グラフ判定がかんたんに書ける。

Submission #19171171 - AtCoder Beginner Contest 187

n, m = map(int, input().split())
graph = [1 << i for i in range(n)]
for _ in range(m):
    a, b = map(int, input().split())
    graph[a - 1] |= 1 << (b - 1)
    graph[b - 1] |= 1 << (a - 1)
dp = [n] * (1 << n)
dp[0] = 0
for s in range(1, 1 << n):
    # 完全部分グラフの場合
    if all((s & graph[i]) == s for i in range(n) if (1 << i) & s):
        dp[s] = 1
        continue
    # その他
    t = s
    while t:
        t = (t - 1) & s
        dp[s] = min(dp[s], dp[t] + dp[s - t])
print(dp[(1 << n) - 1])

類題

U - Grouping