luogu3379 【模板】最近公共祖先(LCA) Tarjan
LCA的Tarjan算法是一个离线算法,复杂度$O(n+q)$。
我们知道Dfs搜索树时会形成一个搜索栈。搜索栈顶节点cur时,对于另外一个节点v,它们的LCA便是v到根节点的路径与搜索栈开始分叉的那个节点lca。而站在cur上枚举v找lca的过程可以用并查集优化到$O(\log n)$级别。
并查集的定义:规定v为已经搜索且已经回溯,当前搜索栈顶为cur,则v并查集中的Father为LCA(cur,v)。查询可直接运用该定义。
并查集的维护:每当搜索栈顶弹出一个节点x时,将x在并查集中的Father设为其在树中的Father。这样x及x的子树的Father就都是这个栈内节点x->Father了。
注意,不要用vector,全部用邻接表,否则慢。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int MAX_NODE = 500010, MAX_PATH = 500010; struct Node; struct Edge; struct Path; struct Link; struct Node { int DfsN; Edge *Head; Node *UnFa;//UnionFather Node *Father; Link *HeadLink; }_nodes[MAX_NODE], *Root; int TotNode; struct Edge { Node *To; Edge *Next; }_edges[MAX_NODE * 2]; int _eCount; struct Path { Node *From, *To; Node *Lca; }_paths[MAX_PATH]; int TotPath; struct Link { Node *To; Path *Query; Link *Next; }_links[MAX_PATH * 2]; int LinkCnt; void AddEdge(Node *from, Node *to) { Edge *e = _edges + ++_eCount; e->To = to; e->Next = from->Head; from->Head = e; } void AddLink(Node *from, Node *to, Path *query) { Link *cur = _links + ++LinkCnt; cur->To = to; cur->Query = query; cur->Next = from->HeadLink; from->HeadLink = cur; } void InitAllPath() { for (int i = 1; i <= TotPath; i++) { AddLink(_paths[i].From, _paths[i].To, _paths + i); AddLink(_paths[i].To, _paths[i].From, _paths + i); } } Node *GetRoot(Node *cur) { return cur->UnFa == cur ? cur : cur->UnFa = GetRoot(cur->UnFa); } void Tarjan(Node *cur, Node *fa) { cur->DfsN = 1; cur->UnFa = cur; cur->Father = fa; for (Edge *e = cur->Head; e; e = e->Next) { if (e->To == cur->Father) continue; Tarjan(e->To, cur); e->To->UnFa = cur; } for (Link *link = cur->HeadLink; link; link = link->Next) if (link->To->DfsN == 2) link->Query->Lca = GetRoot(link->To); cur->DfsN = 2; } int main() { int rootId; scanf("%d%d%d", &TotNode, &TotPath, &rootId); Root = _nodes + rootId; for (int i = 1; i <= TotNode - 1; i++) { int u, v; scanf("%d%d", &u, &v); AddEdge(_nodes + u, _nodes + v); AddEdge(_nodes + v, _nodes + u); } for (int i = 1; i <= TotPath; i++) { int u, v; scanf("%d%d", &u, &v); _paths[i].From = _nodes + u; _paths[i].To = _nodes + v; } InitAllPath(); Tarjan(Root, NULL); for (int i = 1; i <= TotPath; i++) printf("%lld\n", _paths[i].Lca - _nodes); return 0; }