問題
グラフ $G = (V, E)$($|V| = N$, $|E| = M$)が与えられ,各頂点 $i$ に重み $c_i > 0$ が付く。最大重み独立集合の LP 緩和を ADMM で解き,LP 最適値と整数解(丸め)の目的関数値を求めよ。
$$\text{maximize} \sum_{i \in V} c_i x_i \quad \text{s.t.} \quad x_i + x_j \le 1 \; \forall (i,j) \in E, \quad 0 \le x_i \le 1$$制約
| パラメータ | 範囲 | 備考 |
|---|---|---|
| $N$ | $2 \le N \le 200$ | 頂点数 |
| $M$ | $1 \le M \le N(N-1)/2$ | 辺数 |
| $c_i$ | $1 \le c_i \le 100$ | 頂点重み |
入出力例
入力例1
4 4
3 5 2 4
1 2
2 3
3 4
4 1
出力例1
LP: 9.00
Integer: 9
概念図: ADMM の 3 ステップ反復
ヒント
ヒント1(方向性)
ADMM は制約付き最適化を「x の更新」「補助変数 z の更新」「双対変数 u の更新」の 3 ステップに分解する。凸 LP に対して最適解に収束する。
ヒント2(アプローチ)
LP 標準形: $\max c^\top x$, s.t. $Ax \le 1$, $0 \le x \le 1$ を,$z$ を補助変数として $\min -c^\top x$, s.t. $Ax = z, z \le 1, 0 \le x \le 1$ に変換。
- x-update: $(A^\top A + \rho I)x = c/\rho + A^\top(z-u)$ を解いて $[0,1]$ にクリップ
- z-update: $z = \min(Ax + u, 1)$($z \le 1$ への射影)
- u-update: $u = u + Ax - z$(残差の累積)
ヒント3(ほぼ答え)
import numpy as np
rho = 2.0
A = ... # (M, N) 辺-頂点接続行列
M_mat = A.T @ A + rho * np.eye(N)
M_inv = np.linalg.inv(M_mat)
x = np.zeros(N); z = np.ones(M); u_dual = np.zeros(M)
for _ in range(2000):
rhs = c + rho * A.T @ (z - u_dual)
x = np.clip(M_inv @ rhs, 0.0, 1.0)
z = np.minimum(A @ x + u_dual, 1.0)
u_dual = u_dual + A @ x - z
模範解答
import sys
import numpy as np
def solve():
data = sys.stdin.read().split()
idx = 0
N = int(data[idx]); idx+=1
M = int(data[idx]); idx+=1
c = np.array([float(data[idx+i]) for i in range(N)]); idx+=N
edges = []
adj = [[] for _ in range(N)]
for _ in range(M):
u, v = int(data[idx])-1, int(data[idx+1])-1; idx+=2
edges.append((u, v)); adj[u].append(v); adj[v].append(u)
if M == 0:
lp_val = float(sum(c))
print(f"LP: {lp_val:.2f}")
print(f"Integer: {int(lp_val)}")
return
A = np.zeros((M, N))
for k, (u, v) in enumerate(edges):
A[k, u] = 1.0; A[k, v] = 1.0
rho = 2.0
M_mat = A.T @ A + rho * np.eye(N)
M_inv = np.linalg.inv(M_mat)
x = np.zeros(N); z = np.ones(M); u_dual = np.zeros(M)
for it in range(2000):
x_prev = x.copy()
rhs = c + rho * A.T @ (z - u_dual)
x = np.clip(M_inv @ rhs, 0.0, 1.0)
z = np.minimum(A @ x + u_dual, 1.0)
u_dual = u_dual + A @ x - z
if np.max(np.abs(x - x_prev)) < 1e-8: break
lp_val = float(c @ x)
# 整数解: x 値降順に貪欲選択
sel = np.zeros(N, dtype=bool)
for i in np.argsort(-x):
if all(not sel[nb] for nb in adj[i]):
sel[i] = True
int_val = int(c @ sel)
print(f"LP: {lp_val:.2f}")
print(f"Integer: {int_val}")
solve()
Step-by-Step 解説
Step 1: ADMM の基本形
問題: $\min f(x) + g(z)$, s.t. $Ax + Bz = c$
Augmented Lagrangian: $L_\rho = f(x) + g(z) + y^\top(Ax+Bz-c) + \frac{\rho}{2}\|Ax+Bz-c\|^2$
3 ステップの交互更新で $L_\rho$ を最小化。凸問題に対して収束が保証される。
Step 2: LP 緩和への適用
辺制約 $x_i + x_j \le 1$ を補助変数 $z$ に持たせる。x-update は $[0,1]$ ボックス制約への射影(クリップ),z-update は $z \le 1$ への射影(element-wise min)。
Step 3: $M^{-1}$ の前計算
x-update の線形方程式 $(A^\top A + \rho I)x = r$ は毎回同じ左辺 → $M^{-1} = (A^\top A + \rho I)^{-1}$ を事前計算することで各 iter が $O(N^2)$ に。
Step 4: 整数解の丸め戦略
LP 最適値 $x^*$ から整数解を構築:$x_i \ge 0.5$ の頂点を降順に選び,隣接頂点が選ばれていなければ採用(貪欲)。
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| M_mat が特異 | 孤立頂点など | rho * np.eye(N) で常に正定値 (rho > 0) |
| z-update の範囲ミス | $z \ge 0$ も必要と誤解 | 辺制約 $z \le 1$ のみ → np.minimum(..., 1.0) |
| 丸めで独立集合が壊れる | 丸め後に辺チェック不足 | 選択順を x 値降順にして逐次チェック |
次のステップ
- 発展問題: SDP(半正定値計画)+ ADMM による MAX-CUT 近似解法
- 参考: CVXPY(Python の凸最適化ライブラリ)で同問題を解いて ADMM 解と比較
- $\rho$ の適応的調整(Primal/Dual Residual バランシング)
自己評価
理解度: / /
自分の回答:
気づき・メモ: