# By Gemini import sys import heapq from bisect import bisect_left def solve(): # 再帰深度を深く設定 # N が大きい場合 (例: 2*10^5) に備える sys.setrecursionlimit(200010) try: N = int(sys.stdin.readline()) W = list(map(int, sys.stdin.readline().split())) adj = [[] for _ in range(N)] for _ in range(N - 1): A, B = map(int, sys.stdin.readline().split()) u, v = A - 1, B - 1 adj[u].append(v) adj[v].append(u) except EOFError: return except Exception as e: return if N < 3: print(0) return total_sum = sum(W) subtree_sum = [0] * N # グローバル変数として最小差を保持 # (リストやクラス変数にしてミュータブルに渡す) min_diff = [float('inf')] # --- 1. 事前計算: 部分木の重み総和 (DFS) --- def dfs_precompute(v, p): s = W[v] for u in adj[v]: if u == p: continue dfs_precompute(u, v) s += subtree_sum[u] subtree_sum[v] = s dfs_precompute(0, -1) # --- 最小差を更新するヘルパー関数 --- def check_min_diff(S1, S2, S3): if S1 <= 0 or S2 <= 0 or S3 <= 0: # 有効な3分割ではない (重みが0のケースなど) # ただし、問題の制約 (W_i >= 1) があれば不要 return diff = max(S1, S2, S3) - min(S1, S2, S3) if diff < min_diff[0]: min_diff[0] = diff # --- 2. メインのDFS (マージテクニック) --- # v: 現在の頂点, p: 親 # 戻り値: v の部分木内で可能な「切断点の重み」のソート済みリスト def dfs_solve(v, p): # my_desc_sums: v の子孫にあたる切断点の重み (S(w)) のソート済みリスト my_desc_sums = [] for u in adj[v]: if u == p: continue # 1. 子からソート済みリストを受け取る child_desc_sums = dfs_solve(u, v) # --- 2. Case 3 (並列関係) の処理 --- # child_desc_sums (子uの内部) から1つ (S_A) # my_desc_sums (他の子) から1つ (S_B) を選ぶ # O(N log^2 N) にするため、小さい方からイテレート (Small-to-Large) list_A = child_desc_sums list_B = my_desc_sums if len(list_A) > len(list_B): list_A, list_B = list_B, list_A # O(min(|A|,|B|) * log(max(|A|,|B|))) # for S_A in list_A: # target = (total_sum - S_A) / 2 # idx = bisect_left(list_B, target) # for k in [idx, idx - 1]: # if 0 <= k < len(list_B): # S_B = list_B[k] # S_C = total_sum - S_A - S_B # check_min_diff(S_A, S_B, S_C) # O(N log N) のための 尺取り法 O(|A| + |B|) # list_A (S_A) は昇順, target = (T-S_A)/2 は降順 # list_B (S_B) で target に近い点を探すポインタ ptr_B も降順に動く if list_A and list_B: ptr_b = len(list_B) - 1 for S_A in list_A: target = (total_sum - S_A) / 2 # target より大きい S_B をスキップ while ptr_b > 0 and list_B[ptr_b] > target: ptr_b -= 1 # list_B[ptr_b] (<= target) と list_B[ptr_b+1] (> target) が候補 for k in [ptr_b, ptr_b + 1]: if 0 <= k < len(list_B): S_B = list_B[k] S_C = total_sum - S_A - S_B check_min_diff(S_A, S_B, S_C) # 3. リストのマージ (O(|A| + |B|)) my_desc_sums = list(heapq.merge(my_desc_sums, child_desc_sums)) # --- 4. Case 1 (祖先-子孫関係) の処理 --- # 1つ目の切断点を v (重み S_v) # 2つ目の切断点を my_desc_sums の中から (重み S_B) S_v = subtree_sum[v] S_rest = total_sum - S_v # S_B と S_v - S_B が近くなる S_B (S_B approx S_v / 2) を探す target = S_v / 2 idx = bisect_left(my_desc_sums, target) for k in [idx, idx - 1]: if 0 <= k < len(my_desc_sums): S_B = my_desc_sums[k] S_A = S_v - S_B check_min_diff(S_A, S_B, S_rest) # --- 5. 親にリストを渡す --- # v自身も切断点になりうる (vが根でなければ) if p != -1: # S(v) をソートを保ったまま追加 (O(N_v)) # O(N log N) にするためには、最後にマージする # (今回は heapq.merge を使うので O(N_v)) my_desc_sums = list(heapq.merge(my_desc_sums, [S_v])) return my_desc_sums # 根 (頂点0) から探索開始 dfs_solve(0, -1) print(min_diff[0]) if __name__ == "__main__": solve()