luogu3379 【模板】最近公共祖先(LCA) 倍增法
题目大意:给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
整体步骤:1.使两个点深度相同;2.使两个点相同。
这两个步骤都可用倍增法进行优化。定义每个节点的Elder[i]为该节点的2^k(或者说是二进制中的1,10,100,1000...)辈祖先。求它时要利用性质:cur->Elder[i]==cur->Elder[i-1]->Elder[i-1]。具体步骤看代码。
#include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int MAX_FA = 10, MAX_NODE = 500010, MAX_EDGE = MAX_NODE * 2; struct Node; struct Edge; struct Node { int Id, Depth; Edge *Head; Node *Elder[MAX_FA]; }_nodes[MAX_NODE], *Root; int TotNode; struct Edge { Node *From, *To; Edge *Next; }*_edges[MAX_EDGE]; int _edgeCnt; void Init(int root, int totNode) { _edgeCnt = 0; TotNode = totNode; Root = _nodes + root; memset(_nodes, 0, sizeof(_nodes)); } Edge *NewEdge() { return _edges[++_edgeCnt] = new Edge(); } void AddEdge(Node *from, Node *to) { Edge *e = NewEdge(); e->From = from; e->To = to; e->Next = from->Head; from->Head = e; } void Build(int uId, int vId) { Node *u = _nodes + uId, *v = _nodes + vId; u->Id = uId; v->Id = vId; AddEdge(u, v); AddEdge(v, u); } int Log2(int x) { int cnt = 0; while (x / 2) { cnt++; x /= 2; } return cnt; } void Dfs(Node *cur, Edge *FromFa) { if(!FromFa) cur->Depth = 1; else { cur->Elder[0] = FromFa->From; cur->Depth = cur->Elder[0]->Depth + 1; for(int i=1; cur->Elder[i-1]->Elder[i-1]; i++) cur->Elder[i] = cur->Elder[i-1]->Elder[i-1]; } for(Edge *e = cur->Head; e; e=e->Next) if(e->To!=cur->Elder[0]) Dfs(e->To, e); } void DfsStart() { Dfs(Root, NULL); } Node *Lca(Node *deep, Node *high) { if (deep->Depth < high->Depth) swap(deep, high); int len = deep->Depth - high->Depth; for(int k=0; len; k++) { if((1 << k) & len) { deep=deep->Elder[k]; len -= (1 << k);//把len二进制中当前的1去掉 } } if (deep == high) return deep; for (int k = Log2(deep->Depth); k >= 0; k--) { if (deep->Elder[k] != high->Elder[k]) { deep = deep->Elder[k]; high = high->Elder[k]; } } return deep->Elder[0]; } int main() { int totNode, totQ, rootId, uId, vId, id1, id2; scanf("%d%d%d", &totNode, &totQ, &rootId); Init(rootId, totNode); for (int i = 1; i < totNode; i++) { scanf("%d%d", &uId, &vId); Build(uId, vId); } DfsStart(); for (int i = 1; i <= totQ; i++) { scanf("%d%d", &id1, &id2); printf("%d\n", Lca(id1 + _nodes, id2 + _nodes)->Id); } return 0; }
对Lca中for循环正确性的解释:每个整数都可以表示为sum(2^k)。所以以此方式可以到达一个节点的任意辈祖先。
注意:
- k初值有log。
- 树的深度和高度要区分开来。
- Dfs时一开始循环中的判断cur->[k-1]!=NULL是为了处理根节点。