LCA(Least Common Ancestors),即最近公共祖先:在有根树中,找出某两个结点u和v最近的公共祖先(另一种说法,离树根最远的公共祖先)。
使用RMQ解决LCA问题,一篇不错的文档http://wenku.baidu.com/view/392c7023dd36a32d73758193.html,解释了两者之间的转换。
在线算法DFS+ST描述(思想是:将树看成一个无向图,u和v的公共祖先一定在u与v之间的最短路径上):
(1)DFS:从树T的根开始,进行深度优先遍历(将树T看成一个无向图),并记录下每次到达的顶点。第一个的结点是root(T),每经过一条边都记录它的端点。由于每条边恰好经过2次,因此一共记录了2n-1个结点,用E[1, ... , 2n-1]来表示。
(2)计算R:用R[i]表示E数组中第一个值为i的元素下标,即如果R[u] < R[v]时,DFS访问的顺序是E[R[u], R[u]+1, …, R[v]]。虽然其中包含u的后代,但深度最小的还是u与v的公共祖先。
(3)RMQ:当R[u] ≥ R[v]时,LCA[u, v] = RMQ(R[v], R[u]);否则LCA[u, v] = RMQ( R[u], R[v]).
利用树的性质,如果边(a,b)是父子边,即a为b在搜索树中的父亲,则dis[b]=dis[a]+weght[a][b];(dis为某点到根节点的距离)因此树上任意两点间距离=dis[a]+dis[b]-2*dis[lca(a,b)];
2.离线算法Tarjan解决LCA问题
下面是算法的详细讲解:
首先,Tarjan算法是一种离线算法,也就是说,它要首先读入所有的询问(求一次LCA叫做一次询问),然后并不一定按照原来的顺序处理这些询问。而打乱这个顺序正是这个算法的巧妙之处。看完下文,你便会发现,如果偏要按原来的顺序处理询问,Tarjan算法将无法进行。
Tarjan算法是利用并查集来实现的。它按DFS的顺序遍历整棵树。对于每个结点x,它进行以下几步操作:
poj1330和poj 1986都是基础的LCA问题,用于测模板的,注意给出的边是单向还是双向。
1.转RMQ做法
class LCA { int rmq[];// 1~n int n, len, E[];// 图 node buf[];// 边 int F[], pos[], cnt;// 欧拉序列(第i次遍历到的元素),元素第一次遍历到的cnt;posmn[]元素最后一次遍历到的cnt。则{pos[i],posmn[i]}段代表i的子树 int dis[];// 各点到根节点距离 RMQ st; LCA(int maxn) { buf = new node[maxn * 2]; E = new int[maxn]; F = new int[maxn * 2]; rmq = new int[maxn * 2]; pos = new int[maxn]; st = new RMQ(maxn * 2); dis = new int[maxn]; } void init(int n) { this.n = n; len = 0; Arrays.fill(E, -1); } void addedge(int a, int b, int w) { buf[len] = new node(b, E[a], w); E[a] = len++; buf[len] = new node(a, E[b], w); E[b] = len++; } int query(int a, int b) { int k = st.getmin(pos[a], pos[b]); return dis[a] + dis[b] - 2 * dis[F[k]]; } void dfs(int a, int lev) { cnt++; F[cnt] = a; rmq[cnt] = lev; pos[a] = cnt; for (int i = E[a]; i != -1; i = buf[i].ne) { int b = buf[i].be; if (pos[b] != -1) continue; dis[b] = dis[a] + buf[i].weight; dfs(b, lev + 1); cnt++; F[cnt] = a; rmq[cnt] = lev; } } void solve(int root) { cnt = 0; Arrays.fill(pos, -1); Arrays.fill(dis, 0); dfs(root, 0); st.init(2 * n - 1); } }
2.Tarjan做法
void dfs(int a, int p) { vis[a] = 1; for (int i = Eb[a]; i != -1; i = buf[i].ne) { int b = buf[i].be; if (vis[b] == 0) { dis[b] = dis[a] + buf[i].val; dfs(b, a); } } for (int i = Eq[a]; i != -1; i = query[i].ne) { int b = query[i].be; if (vis[b] == 1){ int temp=find(b); ans[query[i].val] = dis[a]+dis[b]-2*dis[temp]; } } f[a] = find(p); }