問題
$N$ 頂点のグラフがある。以下の $Q$ 個のクエリを処理せよ。
- クエリ型 1:
1 u v w— 「$A[v] - A[u] = w$」という関係を追加する。矛盾が生じる場合はconflictと出力せよ。 - クエリ型 2:
2 u v— $A[v] - A[u]$ の値を求めよ。同一成分でない場合はunknownを出力せよ。
制約
| パラメータ | 範囲 |
|---|---|
| $N$ | $1 \le N \le 2 \times 10^5$ |
| $Q$ | $1 \le Q \le 2 \times 10^5$ |
| $|w|$ | $\le 10^9$ |
| $u, v$ | $0 \le u, v < N$ |
入出力例
入力例 1
4 6
1 0 1 3
1 1 2 2
2 0 2
1 0 2 4
1 2 3 -1
2 0 3
出力例 1
5
conflict
4
A[1]-A[0]=3, A[2]-A[1]=2 → A[2]-A[0]=5。A[2]-A[0]=4 は矛盾。A[3]-A[2]=-1 → A[3]-A[0]=4。
概念図: ポテンシャル法の仕組み
ヒント(段階的開示)
ヒント1: 方向性
ポテンシャル法 Union-Find: 各ノードに「根までの重み差
pot[i] = A[i] - A[root(i)]」を管理する。パス圧縮時に差分を累積する。
ヒント2: アプローチ
pot[i]: $A[i] - A[\text{root}(i)]$ の値find(x)でパス圧縮しながらpot[x]を根への差分に変換union(u, v, w): $A[v] - A[u] = w$ を意味する。同一成分なら矛盾チェック
ヒント3: コード骨格
def find(x):
if parent[x] == x:
return x
root = find(parent[x])
pot[x] += pot[parent[x]] # 累積
parent[x] = root
return root
# クエリ型2: A[v] - A[u] = pot[v] - pot[u]
def diff(u, v):
find(u); find(v)
return pot[v] - pot[u]
模範解答 (Python)
import sys
input = sys.stdin.readline
sys.setrecursionlimit(400010)
def main():
N, Q = map(int, input().split())
parent = list(range(N))
rank = [0] * N
pot = [0] * N # pot[i] = A[i] - A[root(i)]
def find(x):
if parent[x] == x:
return x
root = find(parent[x])
pot[x] += pot[parent[x]]
parent[x] = root
return root
def weight(x):
find(x)
return pot[x]
def union(u, v, w):
# A[v] - A[u] = w
wu = weight(u)
wv = weight(v)
ru = find(u)
rv = find(v)
if ru == rv:
return pot[v] - pot[u] == w
# pot[ru] = A[ru] - A[rv] = wv - wu - w
if rank[ru] < rank[rv]:
parent[ru] = rv
pot[ru] = wv - wu - w
elif rank[ru] > rank[rv]:
parent[rv] = ru
pot[rv] = wu - wv + w
else:
parent[rv] = ru
pot[rv] = wu - wv + w
rank[ru] += 1
return True
out = []
for _ in range(Q):
line = list(map(int, input().split()))
if line[0] == 1:
_, u, v, w = line
if not union(u, v, w):
out.append('conflict')
else:
_, u, v = line
ru = find(u)
rv = find(v)
if ru != rv:
out.append('unknown')
else:
out.append(pot[v] - pot[u])
print('\n'.join(map(str, out)))
main()
Step-by-Step 解説
Step 1: ポテンシャルの定義
pot[i] = A[i] - A[root(i)]。パス圧縮時に祖先のポテンシャルを累積することで、常に根への相対値を保持する。
Step 2: 差分クエリ
$A[v] - A[u] = (A[v] - A[\text{root}]) - (A[u] - A[\text{root}]) = \text{pot}[v] - \text{pot}[u]$(同一成分の場合)。
Step 3: Union 時のポテンシャル設定
$A[v] - A[u] = w$ かつ $A[u] = A[\text{ru}] + \text{wu}$, $A[v] = A[\text{rv}] + \text{wv}$ から:
$$A[\text{ru}] - A[\text{rv}] = \text{wv} - \text{wu} - w$$
Step 4: 矛盾検出
同一成分内に union(u, v, w) が来たとき、$\text{pot}[v] - \text{pot}[u] \neq w$ なら矛盾。
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
| パス圧縮順序ミス | pot更新前にparentを書き換える | root = find(parent[x]) の後に pot[x] += pot[parent[x]] |
| union時のpot符号ミス | A[ru]-A[rv]とA[rv]-A[ru]を混同 | 式を紙で導出してから実装 |
| 再帰深度超過 | N=2×10^5で連鎖union | setrecursionlimit or iterative find |
次のステップ
発展: 差分制約系(ベルマンフォード)との等価性を証明。関連: 重み付き DSU を用いた尺取り法・グラフ染色問題。