少ない学びをせめて記録する

技術記録、競プロメモ、その他調べたことを書く @京都, twitter : @nehan_der_thal

ABC148-F Playing Tag on Treeを解いた

atcoder.jp

ABC148に出た。全完だったが簡単だったようでパフォーマンスは大したことなかった。

木の上で高橋くん(以下T)と青木くん(以下A)が鬼ごっこする問題、高橋くんから順に互いに1ずつ移動する。Aが鬼。最も長く移動した場合のAの移動距離を求める。 基本的にAの初期位置からTの移動可能な中で最も遠いノード(Nt)までの距離-1が正解になる。

Ntまでの距離を-1する場合としない場合を偶奇などで分けるべきか本番中迷った。 AはA1,A2,,,Ak-1, Ak(=Nt)と移動すると考えると、 偶奇によって両者の行動は

T:T1
A:A1
...
T : Ak
A : Ak-1
T: Ak-1 → 終了

となる場合と

T:T1
A:A1
...
T : Ak-1
A:Ak-1 →終了

となる場合があるが、回答はAの移動距離なのでどちらも同じになる。 まずTから木を探索して各ノードのTからの距離を計算。 その後Aからもう一度探索、ここで(Tからの距離>=Aからの距離)となっていてノードはTは到達できない。そのようなノードに到達した場合、探索における次ノードは子孫にTがいるノードとなる。

それと本番中はpython再帰を書かないようにしようとして詰まり結局再帰で出した。コンテスト終了後に非再帰を通した。以下ははじめに出した再帰のもの。 混乱してしまい汚いコードになった。globalの使い方忘れてdictionaryの値をglobal変数として使ったり。

import copy
import sys
sys.setrecursionlimit(100000)
input = sys.stdin.readline

N,U,V = map(int, input().split())
d = [set() for _ in range(N+1)]
dps = [0 for _ in range(N+1)]
dpss = [0 for _ in range(N+1)]
vs = set()
r = {}

def it(v, p, depth, ps):
    for u in d[v]:
        if u == p:
            continue
        dps[u] = depth
        if u == V:
            r["p"] = copy.deepcopy(ps)
            continue
        ps.add(u)
        it(u, v, depth+1,ps)
        ps.remove(u)
        vs.add(u)

def it2(v, p, depth):
    for u in d[v]:
        if u == p:
            continue
        if u not in vs:
            continue
        if dps[v] >= depth-1 and u not in r["p"]:
            continue
        dpss[u] = depth
        it2(u, v, depth+1)

for _ in range(N-1):
    a, b = [int(x) for x in input().split()]
    d[a].add(b)
    d[b].add(a)

vs.add(U)
it(U, -1, 1, set([U]))
it2(V, -1, 1)
print(max(dpss)-1)

コンテスト後に非再帰にしたのは以下。

import sys
input = sys.stdin.readline
N,U,V = map(int, input().split())
d = [set() for _ in range(N+1)]
dps = [0 for _ in range(N+1)]
dpss = [0 for _ in range(N+1)]

def it(v):
    stack = [v]
    vs = set()
    path = []
    while True:
        if not len(stack):
            break
        v = stack.pop()
        if v > 0:
            path.append(v)
            vs.add(v)
            for u in d[v]:
                if u in vs:
                    continue
                if u == V:
                    r = path[:]
                dps[u] = dps[v] + 1
                stack.append(-v)
                stack.append(u)
        else:
            path.pop()
    return r

def it2(v, path):
    stack = [v]
    vs = set()
    while True:
        if not len(stack):
            break
        v = stack.pop()
        vs.add(v)
        for u in d[v]:
            if u in vs:
                continue
            if dps[v] >= dpss[v] and u not in path:
                continue
            dpss[u] = dpss[v] + 1
            stack.append(u)

for _ in range(N-1):
    a, b = [int(x) for x in input().split()]
    d[a].add(b)
    d[b].add(a)

path = set(it(U))
it2(V, path)
print(max(dpss)-1)

以下のpathの保持の部分が少し苦戦したので要復習。親ノード番号を*-1してstackにappendすることで深さ優先時、今の位置を保存できる。

    stack = [v]
    vs = set()
    path = []
    while True:
        if not len(stack):
            break
        v = stack.pop()
        if v > 0:
            path.append(v)
            vs.add(v)
            for u in d[v]:
                if u in vs:
                    continue
                if u == V:
                    r = path[:]
                    continue
                dps[u] = dps[v] + 1
                stack.append(-v)
                stack.append(u)
        else:
            path.pop()