子ノードの情報を結合してソート
Editorial - Denso Create Programming Contest 2022(AtCoder Beginner Contest 239)の方法.毎回ソートして間に合う!
考え方
各頂点$v$に対し,「頂点$v$の部分木に含まれる頂点に書かれた整数が降順にソートされたリスト$P_{v}$」を求めておけば,各クエリを$O(1)$で処理できる.葉から根の方へ親をたどって$P_{v}$を求めていく.
ある頂点$v$を見ているとき,その頂点のそれぞれの子ノード$v_{i}$に対して「その子ノードの部分木に含まれる頂点に書かれた整数が降順にソートされたリスト$P_{v_{i}}$」がわかっているとする.
このとき,$P_{v}$は,子ノードの情報$\{P_{v_{i}}\}_{i}$(と頂点$v$に書かれた整数)をマージして降順にソートすることで求められる.
$1 \leq K_{i} \leq 20$だから,$P_{v}$は大きい方から20個の値を保持しておけば(その親ノードの計算をするのに)十分である.
計算量は,すべての頂点$v$に対し,その子ノード$\{v_{i}\}_{i=1}^{N_{v}}$のリスト(要素数$20$)をマージ($O(20N_{v})$)してソート($O(20N_{v} \log(20N_{v}))$)するので,合計で
&O\biggl( \sum_{v} 20N_{v} \log(20N_{v}) \biggr) \\
&= O\biggl( \log \prod_{v} (20N_{v})^{20N_{v}} \biggr) \\
&\leq O\biggl( \log (20N)^{\sum_{v} 20 N_{v}} \biggr) \\
&=20N \log (20N)
\end{aligned}
回答例
頂点を入れるスタックを用意する.はじめ,スタックには根だけが入っているとする.また,頂点をすでに見たかどうかを管理する(頂点の情報と一緒にスタックに入れる,別なリストを用意る,など).
スタックからpopした頂点を初めてみる場合は,その頂点を「チェック済み」にした上でスタックにpushする.その後,子ノードを「未チェック」の状態でスタックにpushする.
こうすると,スタックからpopした頂点が「チェック済み」のとき,その部分木は全て「チェック済み」になっている.よって,この処理を使えば,「葉から順に見ていく」ことが可能である.
これは,結局オイラーツアー(Euler tour technique - Wikipedia)の順に見ていることになる.
N, Q = map(int, input().split()) X = list(map(int, input().split())) G = [[] for _ in range(N)] for _ in range(N - 1): A, B = map(int, input().split()) A -= 1 B -= 1 G[A].append(B) G[B].append(A) P = [[x] for x in X] stack = [(-1, 0)] seen = [False] * N while stack: par, cur = stack.pop() if not seen[cur]: seen[cur] = True stack.append((par, cur)) for chi in G[cur]: if chi != par: stack.append((cur, chi)) else: for chi in G[cur]: if chi != par: P[cur] += P[chi] P[cur] = sorted(P[cur], reverse = True)[:20] for _ in range(Q): V, K = map(int, input().split()) print(P[V - 1][K - 1])
【参考】Submission #29438202 - Denso Create Programming Contest 2022(AtCoder Beginner Contest 239)