公式

F - Jump Traveling 解説 by toam


一回の移動を \(K\) 歩とみなすことにします.次のような dp を考えます.

\(\mathrm{dp}[(u,v)][k]\):頂点 \(u\) から \(v\) へ,一回の移動の中の \(k\) 歩目で移る場合を考えたとき,これが起きうるような頂点 \(v\) までの歩数の最小値

頂点 \(1\) に隣接する頂点 \(v\) に対して,\(\mathrm{dp}[(1,v)][1]=1\) と初期化できます.(あるいは仮想の頂点 \(X\) を作って \(\mathrm{dp}[(X,1)][K]=0\) としても良いです.)dp の遷移は次のようになります.

  • \(k\neq K\) のとき: 頂点 \(v\) に隣接する頂点 \(w\)(ただし \(w\neq u\))ついて,\(\mathrm{dp}[(v,w)][k+1]\leftarrow \mathrm{dp}[(u,v)][k]+1\)

  • \(k=K\) のとき: 頂点 \(v\) に隣接する頂点 \(w\) について,\(\mathrm{dp}[(v,w)][1]\leftarrow \mathrm{dp}[(u,v)][k]+1\)

これは BFS で計算することができますが,このままでは TLE してしまいます.なぜなら,ある \(v\) に対して \(\mathrm{dp}[(v,w)][k+1]\leftarrow \mathrm{dp}[(u,v)][k]+1\) という遷移が起きる回数を考えると,\(v\) の次数を \(d\) としたときに, \(u,w\) がそれぞれ \(d\) 通り,\(k\)\(K\) 通りあるため,最悪で \(\Theta(N^2K)\) 回になってしまうからです.

ここで重要な考察として,ある頂点 \(v\) に対して,\(\mathrm{dp}[(v,w)][k+1]\leftarrow \mathrm{dp}[(u,v)][k]+1\) という遷移は \(2\) つの \(u\) に対してのみ行えば十分です.言い換えると,頂点 \(v\)\((k+1)\) 歩目で踏み入れるのは \(2\) 回まで考慮すればそれで十分です.

その理由を次の図で説明します.

  1. 初めて \(v\)\(a\) から訪れたとき,\(v\to b, v\to c, v\to d\) の遷移が起きます.
  2. 次に \(v\)\(b\) から訪れたとき,更新される可能性があるのは \(v\to a\) のみです.\(v\to c\)\(v\to d\)\(1\) 回目に \(a\) から訪れたときにすでに遷移が起きています.
  3. \(v\)\(3\) 回目以降訪れたとき,\(1\) 回目と \(2\) 回目ですべて遷移は網羅しているため,新しい遷移は起きません.

よって,\(v\)\(k\) 歩目に踏み入れたのが何回か?を持っておき, \(3\) 回目以降の処理をスキップすることで,計算量は \(O(NK)\) になります.

実装についてですが,各辺について双方向の有向辺があるとみなし,元のグラフで \(i\) 番目(0-indexed)の辺に対応する有向辺を \(2i,2i+1\) と番号付けると連想配列等を使わずに実装できます.

from collections import deque


def solve():
    N, K = map(int, input().split())
    G = [[] for i in range(N)]
    for i in range(N - 1):
        u, v = map(lambda x: int(x) - 1, input().split())
        G[u].append((v, 2 * i))
        G[v].append((u, 2 * i + 1))

    dp = [[-1] * (2 * N - 2) for i in range(K)]
    ans = [-1] * N
    cnt = [[0] * N for i in range(K)]
    dq = deque([(0, 0, -1)]) # (総歩数,頂点番号,有向辺の番号)
    while dq:
        d, v, e = dq.popleft()
        if d % K == 0 and ans[v] == -1:
            ans[v] = d // K
        if cnt[d % K][v] == 2:
            continue
        cnt[d % K][v] += 1
        for u, e2 in G[v]:
            if e ^ e2 == 1 and d % K != 0:
                continue
            if dp[(d + 1) % K][e2] == -1:
                dp[(d + 1) % K][e2] = d + 1
                dq.append((d + 1, u, e2))

    print(*ans[1:])


for _ in range(int(input())):
    solve()

投稿日時:
最終更新: