用 LCA 枚举路径交点候选,并用 DFS 序祖先关系判断两条路径是否相交。
OJ: luogu
题目 ID: P3398
难度:普及+/提高-
标签:LCA路径相交树python
日期: 2026-07-17 02:00
题意
判断树上路径 a-b 与 c-d 是否有公共节点。
思路
两条路径的交点若存在,必出现在六个端点两两 LCA 以及两条路径各自 LCA 中。用 DFS 序区间判断祖先关系,节点 x 在路径 a-b 上当且仅当它是 lca(a,b) 的后代且为 a 或 b 的祖先;枚举候选即可。
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)。
总结
树上路径相交不必真的走路径,找到有限个结构性候选点即可。