求LCA的几种方法

来源:互联网 发布:虚拟网络运营商 编辑:程序博客网 时间:2024/06/05 23:56

方法一:暴力

最容易想到的方法,固然就是暴力,即一个一个往父亲节点跳,但很明显,这样很傻.


方法二:倍增(ST)

我们考虑设f[i][j]表示i2j个父亲.

便有:

int lca(int x,int y){    if (deep[x] < deep[y]) swap(x,y);    for (int i = maxer; i >= 0; i--)        if (deep[f[x][i]] >= deep[y]) x = f[x][i];    if (x==y) return;    for (int i = maxer; i >= 0; i--)        if (f[x][i]!=f[y][i]) x = f[x][i], y = f[y][i];    return f[x][0]; //注意此时的f[x][0]才是LCA.}

方法三:Tarjan

用Tarjan求LCA,更适用于离线求多对点的LCA,可以做到O(n+m+q)

具体方法不多讲,很简单.

核心思想是记录一个fa[i]表示i的父亲,每次走到一个点,判断与这个点的LCA是否已经算过,算过就马上跳到父亲,直至俩父亲相等.


方法四:DFS序

我们需要注意,用DFS序去求LCA很简单,但是如果要求一些如区间最大最小之类的,就比较麻烦.

所以,我们在求解比较复杂的LCA问题时,还是尽量用倍增.

不过用DFS序求LCA也很简单.

我们求出一棵树的dfs序,以及每个点在dfs序中第一次出现的位置,那么两个点的LCA,就是这两个点在dfs 序中第一次出现位置的区间里深度最小的那个节点.

RMQ维护即可.

以下代码转自百度(实在找不到LCA裸题)

int tot, seq[N << 1], pos[N << 1], dep[N << 1];// dfs过程,预处理深度dep、dfs序数组seqvoid dfs(int now, int fa, int d) {    pos[now] = ++tot, seq[tot] = now, dep[tot] = d;    for (int i = head[now]; i; i = e[i].next) {        int v = e[i].to;        if (v == fa) continue;        dfs(v, now, d + 1);        seq[++tot] = now, dep[tot] = d;    }}int anc[N << 1][20]; // anc[i][j]表示i节点向上跳2^j层对应的节点void init(int len) {    for (int i = 1; i <= len; i++)        anc[i][0] = i;    for (int k = 1; (1 << k) <= len; k++)        for (int i = 1; i + (1 << k) - 1 <= len; i++)            if (dep[anc[i][k - 1]] < dep[anc[i + (1 << (k - 1))][k - 1]])                anc[i][k] = anc[i][k - 1];            else                anc[i][k] = anc[i + (1 << (k - 1))][k - 1];}int rmq(int l, int r) {    int k = log(r - l + 1) / log(2);    return dep[anc[l][k]] < dep[anc[r + 1 - (1 << k)][k]] ? anc[l][k] : anc[r + 1 - (1 << k)][k];}int calc(int x, int y) {    x = pos[x], y = pos[y];    if (x > y) swap(x, y);    return seq[rmq(x, y)];}int lca(int a, int b) {    dfs(root, 0, 1); // root为树根节点的编号    init(tot);    return calc(a, b);}

方法五:树链剖分

  • 一开始以为树链剖分很高级,其实,是个很简单的算法.

  • 我们设

    • size[u]表示u的子树大小.

    • son[u]表示u在一条重链中的儿子.

    • deep[u]表示u的深度

    • fa[u]表示u的父亲

    • top[u]表示u所在重链的顶端.

    • pos[u]表示u在线段树中的标号.

  • 我们把一个根节点u,所连向的所有vsize值最大的,称为重儿子,其中(u,v)这条边称为重边.

  • 除重边外的其它边称为轻边.

  • 树链剖分完后的树有以下一个重要性质:

    • 重链以及轻链都不会超过log2n条.

    • 证明如下:因为每次往轻边走,size至少减少一般,所以最多有log2n条轻边,两条重链之间至少有一条轻边,所以至多有log2n条重链.

  • pos[u]是为了求一些类似于:

    • 询问一条路径和,修改一段路径和
  • 如果只要求LCA. 我们可以采用以下的方法:

  • 设所求LCA的为(u,v).

  • 判断,如果u,v在同一重链上时,则深度较小的那个点为LCA.

  • 否则,如果u,v不在同一重链上时,则比较,如果deep[top[u]]>deep[top[v],我们就把u跳到top[u],否则把v跳到top[v]

  • 如果有top[u]==u或者top[v]==v,我们就跳到它的父亲.

  • 这样,当u==v时,即为LCA.

  • 简单证明:

    • 一条重链不可能有两个分支,所以,当两个点不在同一条重链上时,跳深度较深的那个点,一定不会超过LCA,最后一次跳,也不会跳出LCA.
原创粉丝点击