E - 社内連絡網 / Internal Contact Network Editorial
by
harurun4635
問題概要は以下の通りです。
根つき木が与えられます。以下のクエリを答えてください。
\(K\) 要素の頂点の部分集合 が与えられ、この集合について
- クエリ \(1\) : LCA から各頂点への距離の総和
- クエリ \(2\) : \(1\) 頂点取り除いた \(K-1\) 頂点における「 LCA から各頂点への距離の総和」の最小値
以降「 \(2\) 頂点の LCA をクエリ \(O(\log N)\) で求めるデータ構造」が既知であるとしましょう。
このデータ構造には様々なものが知られています。ゼロから書くのであればダブリングが簡単で、前計算 \(O(N \log N)\)、クエリ \(O(\log N)\) です。前計算 \(O(N)\)、クエリ \(O(1)\) まで高速化できますが、詳細はここでは省略します。
複数頂点の LCA
まず、複数頂点の LCA は \(2\) 頂点の LCA を繰り返しとることで求まります。
つまり \(S = \{v_1, v_2, \dots, v_K \}\) として、\(\operatorname{LCA}( S ) = \operatorname{LCA}(\operatorname{LCA}(\operatorname{LCA}(v_1, v_2), \ldots,v_{K-1}), v_K)\)
となります。
この証明は帰納法で簡単にできます。
クエリ \(1\) はこの頂点からの距離をそれぞれ求めればよいので、 \(O(K \log N)\) で解決できます。
クエリ \(2\) の解法
\(\operatorname{LCA}(S \setminus \{v_i\})\) の求め方を考えてみましょう。先程と同様の議論から
\(\operatorname{LCA}(S \setminus \{v_i\}) = \operatorname{LCA}(\operatorname{LCA}(\{v_1, \dots, v_{i-1}\}), \operatorname{LCA}(\{v_{i+1}, \dots, v_K\}) )\) であることを踏まえると、
すべての \(i\) について
- \(\operatorname{LCA}(\{v_1, \dots, v_{i-1}\})\)
- \( \operatorname{LCA}(\{v_{i+1}, \dots, v_K\})\)
が求まっていればよいです。これは、prefix と suffix それぞれについて LCA を求めればよいということであり、明らかに可能です。
問題は、LCA からの距離の総和(以降 \(\operatorname{dis}(S)\) とします)です。\(S\) の分割 \(S_1, S_2\) に対して \(l = \operatorname{LCA}(S), l_1 = \operatorname{LCA}(S_1), l_2 = \operatorname{LCA}(S_2)\) とすると
- \(\operatorname{dis}(S) = \operatorname{dis}(S_1) + \operatorname{dis}(S_2) + (l - l_1)|S_1| + (l-l_2)|S_2| \) となるはずですから、\(\operatorname{dis}, \operatorname{LCA}, |S|\)の情報をもつことで同様に prefix, suffix に対してすべての値を管理できます。
実装例
import sys
input = sys.stdin.readline
n, q = map(int, input().split())
log = n.bit_length()
up = [[-1] * n for _ in range(log)]
dep = [0] * n
p = list(map(int, input().split()))
for v in range(1, n):
up[0][v] = p[v - 1] - 1
dep[v] = dep[up[0][v]] + 1
for k in range(1, log):
for v in range(n):
p = up[k - 1][v]
if p != -1:
up[k][v] = up[k - 1][p]
def lca(u, v):
if dep[u] < dep[v]:
u, v = v, u
d = dep[u] - dep[v]
for k in range(log):
if d >> k & 1:
u = up[k][u]
if u == v: return u
for k in range(log - 1, -1, -1):
if up[k][u] != up[k][v]:
u = up[k][u]
v = up[k][v]
return up[0][u]
def merge(a, b):
ans1, l1, s1 = a
ans2, l2, s2 = b
if s1 == 0: return b
if s2 == 0: return a
l = lca(l1, l2)
ans = ans1 + ans2
ans += (dep[l1] - dep[l]) * s1
ans += (dep[l2] - dep[l]) * s2
return ans, l, s1 + s2
for _ in range(q):
a = list(map(int, input().split()))
k = a[0]
v = [x - 1 for x in a[1:]]
pre = [(0, -1, 0)] * (k + 1)
suf = [(0, -1, 0)] * (k + 1)
for i in range(k):
pre[i + 1] = merge(pre[i], (0, v[i], 1))
for i in range(k - 1, -1, -1):
suf[i] = merge((0, v[i], 1), suf[i + 1])
ans1 = pre[k][0]
if k == 1:
print(ans1, -1)
continue
ans2 = 10**30
for i in range(k):
ans2 = min(ans2, merge(pre[i], suf[i + 1])[0])
print(ans1, ans2)