問題
$N$ 頂点の木が与えられる。各頂点 $v$ に対して、「$v$ を根としたとき、各頂点の深さの総和」$S_v = \sum_{u=0}^{N-1} \text{depth}_v(u)$ を全頂点について求めよ。
制約
| パラメータ | 範囲 | 備考 |
|---|---|---|
| $N$ | $2 \le N \le 2 \times 10^5$ | 頂点数 |
| 辺 | $N-1$ 本 | 木(連結・閉路なし) |
入出力例
入力例1
5
0 1
0 2
1 3
1 4
出力例1
6 5 10 8 8
$S_0 = 0+1+1+2+2 = 6$。$S_1 = 1+0+2+1+1 = 5$。$S_2 = 1+2+0+3+3 = 9$(木の構造による)。
概念図: Rerooting の遷移
根を $p$ から $v$(部分木サイズ $\text{sz}[v]$)に移すと、$v$ の部分木の $\text{sz}[v]$ 頂点は深さが 1 減り、それ以外の $N - \text{sz}[v]$ 頂点は深さが 1 増える。
ヒント
ヒント1(方向性)
素朴に各頂点を根として BFS すると $O(N^2)$。全方位木 DP(Rerooting)では、まず 1 頂点 (0) を仮根にして 下向き DP で $\text{sz}[v]$(部分木サイズ)と $\text{dp}[v]$($v$ の部分木内の深さ総和)を計算し、次に 上向き伝播 で全頂点の $S[v]$ を $O(N)$ で求める。
ヒント2(アプローチ)
- 下向き DFS(post-order): $\text{dp}[p] \mathrel{+}= \text{dp}[v] + \text{sz}[v]$ で「子の深さ総和 + 部分木サイズ(辺1本分の深さ増加)」を集約
- $S[0] = \text{dp}[0]$(根 0 からの全頂点深さ総和)
- 上向き伝播(pre-order): $S[v] = S[p] + N - 2 \cdot \text{sz}[v]$
ヒント3(ほぼ答え)
# 上向き伝播
for v in order: # BFS 順(pre-order)
for u in adj[v]:
if u != parent[v]:
# u を根にすると:
# - u の部分木 sz[u] 頂点: 深さ -1 (v→u の辺が根方向になる)
# - 残り N-sz[u] 頂点: 深さ +1
S[u] = S[v] + N - 2 * sz[u]
模範解答
import sys
input = sys.stdin.readline
def solve():
N = int(input())
adj = [[] for _ in range(N)]
for _ in range(N - 1):
u, v = map(int, input().split())
adj[u].append(v); adj[v].append(u)
root = 0
sz = [1] * N; dp = [0] * N
parent = [-1] * N; order = []
# BFS で pre-order 列を作成(DFS も可)
from collections import deque
q = deque([root]); parent[root] = -1
while q:
v = q.popleft(); order.append(v)
for u in adj[v]:
if u != parent[v]:
parent[u] = v; q.append(u)
# post-order で下向き DP
for v in reversed(order):
p = parent[v]
if p != -1:
dp[p] += dp[v] + sz[v]
sz[p] += sz[v]
# 上向き伝播
S = [0] * N; S[root] = dp[root]
for v in order:
for u in adj[v]:
if u != parent[v]:
S[u] = S[v] + N - 2 * sz[u]
print(*S)
solve()
Step-by-Step 解説
Step 1: 下向き DP の直観
頂点 0 を根として、$\text{dp}[v]$ = 「頂点 $v$ の部分木内の頂点について、$v$ からの相対深さの総和」を post-order で計算する。子 $c$ の部分木の全頂点は $v$ から見ると深さが 1 増えるので:
$$\text{dp}[v] \mathrel{+}= \text{dp}[c] + \text{sz}[c]$$
Step 2: 根の移し替えの遷移式
根を $p$ から子 $v$ に移すと($\text{sz}[v]$ は旧根基準の $v$ の部分木サイズ):
- $v$ の部分木の $\text{sz}[v]$ 頂点: 全員の深さが 1 減る
- それ以外の $N - \text{sz}[v]$ 頂点: 全員の深さが 1 増える
$$S[v] = S[p] + (N - \text{sz}[v]) - \text{sz}[v] = S[p] + N - 2 \cdot \text{sz}[v]$$
Step 3: BFS 順(pre-order)で伝播
BFS で得た order はそのまま pre-order。親 $p$ の $S[p]$ が確定してから子 $v$ の $S[v]$ を計算するため、order の順序のまま処理すればよい。
Step 4: 計算量
BFS $O(N)$ + post-order 集約 $O(N)$ + 伝播 $O(N)$ で全体 $O(N)$。
よくあるミス
| ミス | 原因 | 正しい書き方 |
|---|---|---|
sz[v] の更新タイミング | pre-order では未確定 | reversed(order)(post-order)で更新 |
根の $S[0]$ を dp[0] にしない | dp は相対深さ | S[root] = dp[root] |
| 伝播で親を上書き | u != parent[v] チェック漏れ | 必ず子方向のみ更新 |
次のステップ
- 発展問題: 辺重みが存在する場合の全頂点距離和、またはパス上の辺重み最大値の全頂点版(部分木マージで拡張)
自己評価
理解度:
自分の回答:
気づき・メモ: