仓鼠找 sugar

GitHub跳转原题关系图返回列表

用 LCA 枚举路径交点候选,并用 DFS 序祖先关系判断两条路径是否相交。

OJ: luogu

题目 ID: P3398

难度:普及+/提高-

标签:LCA路径相交python

日期: 2026-07-17 02:00

题意

判断树上路径 a-bc-d 是否有公共节点。

思路

两条路径的交点若存在,必出现在六个端点两两 LCA 以及两条路径各自 LCA 中。用 DFS 序区间判断祖先关系,节点 x 在路径 a-b 上当且仅当它是 lca(a,b) 的后代且为 ab 的祖先;枚举候选即可。

Python 知识

  • DFS 序和子树大小把祖先判断变成两个整数区间比较。
  • 候选集合用元组保存,最多六个节点,不需要构造路径列表。
  • 倍增 LCA 函数可以复用在多个候选上。

代码

python
import sys
from array import array


input = sys.stdin.buffer.readline
n, queries = map(int, input().split())
graph = [[] for _ in range(n + 1)]
for _ in range(n - 1):
    u, v = map(int, input().split())
    graph[u].append(v)
    graph[v].append(u)

parent = array("i", [0]) * (n + 1)
depth = array("i", [0]) * (n + 1)
entry = array("i", [0]) * (n + 1)
leave = array("i", [0]) * (n + 1)
order = array("i", [1])
depth[1] = 1
for node in order:
    for neighbor in graph[node]:
        if neighbor != parent[node]:
            parent[neighbor] = node
            depth[neighbor] = depth[node] + 1
            order.append(neighbor)

# Build a real DFS preorder; unlike the parent-building BFS order, it makes
# every subtree a contiguous interval.
subtree = array("i", [1]) * (n + 1)
for node in reversed(order[1:]):
    subtree[parent[node]] += subtree[node]
preorder = []
stack = [1]
while stack:
    node = stack.pop()
    preorder.append(node)
    for neighbor in reversed(graph[node]):
        if neighbor != parent[node]:
            stack.append(neighbor)
for index, node in enumerate(preorder, 1):
    entry[node] = index
    leave[node] = index + subtree[node] - 1

ancestors = [parent]
for _ in range(1, n.bit_length()):
    previous = ancestors[-1]
    ancestors.append(array("i", (previous[previous[node]] for node in range(n + 1))))


def lca(x, y):
    if depth[x] < depth[y]:
        x, y = y, x
    difference = depth[x] - depth[y]
    bit = 0
    while difference:
        if difference & 1:
            x = ancestors[bit][x]
        difference >>= 1
        bit += 1
    if x == y:
        return x
    for level in range(len(ancestors) - 1, -1, -1):
        if ancestors[level][x] != ancestors[level][y]:
            x = ancestors[level][x]
            y = ancestors[level][y]
    return parent[x]


def is_ancestor(x, y):
    return entry[x] <= entry[y] <= leave[x]


def on_path(node, x, y, ancestor):
    return is_ancestor(ancestor, node) and (is_ancestor(node, x) or is_ancestor(node, y))


answers = []
for _ in range(queries):
    a, b, c, d = map(int, input().split())
    ab, cd = lca(a, b), lca(c, d)
    candidates = (ab, cd, lca(a, c), lca(a, d), lca(b, c), lca(b, d))
    meet = any(on_path(node, a, b, ab) and on_path(node, c, d, cd) for node in candidates)
    answers.append('Y' if meet else 'N')
print("\n".join(answers))

原有 C++ 版本仍保留:

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-17 01:04
 * update_at: 2026-07-17 01:04
 */
#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    return 0;
}

复杂度

每次询问进行常数次 O(log n) LCA,空间 O(n log n)

总结

树上路径相交不必真的走路径,找到有限个结构性候选点即可。