LCA最近公共祖先

很久以前学习过,看了很多花花绿绿的博客,但好像理解错了。直到最近打了一道非严格次小生成树(Kruskal+LCA),才发现我写的整个倍增全是错的,然后树剖、塔尖一点都想不起来,所以在一个无聊的下午整理整理。

一、倍增

O(nlgn)建表,询问O(lgn),直接上两段代码(因为具体思路挺难说明白的):

void DFS(int u, int fa)//一遍dfs求出f数组(或者还有g数组,记录长度啊,最值啊之类的东西)
{
    f[u][0] = fa;
    for (int i = 1; i <= 17; i++) //u向上跳(1<<i)次到的点就是他跳(1<<(i-1))再跳(1<<(i-1))
        f[u][i] = f[f[u][i-1]][i-1], g[u][i] = g[u][i-1]+g[f[u][i-1]][i-1];
    for (int i = 0; i < to[u].size(); i++){
        int v = to[u][i];
        int w = val[u][i];
        if (v == fa) continue;
        dpt[v] = dpt[u]+1;
        g[v][0] = w;
        DFS(v, u);
    }
}
void LCA(int x, int y, int &u, int &l)//求x和y的LCA,再顺便求两个点之间的距离(或者其他东西)
{
    l = 0;
    if (x < y) swap(x, y);
    for (int i = 17; i >= 0; i--)//如果深度不同先跳成相同
        if (dpt[x]-(1<<i) >= dpt[y])
            l += g[x][i], x = f[x][i];
    for (int i = 17; i >= 0; i--)
        if (f[x][i] != f[y][i]){//跳到不一样的祖先节点,防止跳过头
            l += g[x][i]+g[y][i];
            x = f[x][i], y = f[y][i];
        }
    if (x != y) l += g[x][0]+g[y][0], x = f[x][0];//把最后一步补上
    u = x;
}

大致思路就是酱紫

二、tarjan

这是一个离线算法,O(n+q)。
大致步骤:(摘自JVxie的博客)

1.任选一个点为根节点,从根节点开始。
2.遍历该点u所有子节点v,并标记这些子节点v已被访问过。
3.若是v还有子节点,返回2,否则下一步。
4.合并v到u上。
5.寻找与当前点u有询问关系的点e。
6.若是e已经被访问过了,则可以确认u和e的最近公共祖先为e被合并到的父亲节点。

void Tarjan(int u)//marge和find为并查集合并函数和查找函数
{
    fa[u] = u;
    vis[u] = 1;   //标记u被访问过;
    for (int i = 0; i < to[u].size(); i++){    //访问所有u子节点v
        int v = to[u][i];
        if (!vis[v]){
            Tarjan(v);    //继续往下遍历
            fa[v] = u;    //合并v到u
        }
    }
    for (int i = 0; i < query[u].size(); i++){   //访问所有和u有询问关系的e
        int e = query[u][i];
        int fa;
        if (vis[e]) FIND(e); //LCA就是e的祖先
    }
}

怎么证明他是对的呢?首先可以手膜,找个几组数据一模就懂了。
然后是正经的证明:

在对于某个节点u处理他的询问时,已访问的e有两种状态。
1. 一个是在u的子树里,那他已经被连到u上了
2. 然后就是不在u的子树里,这种情况下tarjan()必定是从e一路回溯到LCA然后再向下搜到u的。如果回溯到的最高点深度小于于LCA了,那时整个u所在的子树都已经被搜过一遍,不会再搜到u,不可能存在这种情况。如果回溯到的点比LCA深,那u和e还是被隔离开的,也就是说e还没被访问,与条件不符。

三、RMQ

利用欧拉序(就是搜到就记,回溯还记录的那个,上面那张图的欧拉序:1-2-5-2-6-2-1-3-7-3-8-9-11-9-8-10-12-10-8-3-1-4-1)因为这个奇怪的序列的某些奇怪的性质,在某两个点最早出现的位子之间的那些点里,深度最浅的就是LCA了。
于是问题转化为了RMQ(区间最小值),用st表求解,O(nlgn)建表,O(1)询问。

四、树链剖分

懒懒懒懒懒懒懒懒懒懒懒懒懒懒懒懒
剖轻重链,如果两个点在同一根链上,LCA就是深度较浅的那个点。如果不在同一条链上,就选深的点往上跳。具体实现还挺简单的,比前面几种都要短。

猜你喜欢

转载自blog.csdn.net/xyyxyyx/article/details/81098820