初学LCA---最近公共祖先 离线与在线

首先是最近公共祖先的概念(什么是最近公共祖先?):
转载:https://www.cnblogs.com/JVxie/p/4854719.html
在一棵没有环的树上,每个节点肯定有其父亲节点和祖先节点,而最近公共祖先,就是两个节点在这棵树上深度最大的公共的祖先节点。
换句话说,就是两个点在这棵树上距离最近的公共祖先节点。
所以LCA主要是用来处理当两个点仅有唯一一条确定的最短路径时的路径。

常用的求LCA的算法有:Tarjan/DFS+ST/倍增
什么是Tarjan(离线)算法呢?顾名思义,就是在一次遍历中把所有询问一次性解决,所以其时间复杂度是O(n+q)。
    Tarjan算法的优点在于相对稳定,时间复杂度也比较居中,也很容易理解。
    下面详细介绍一下Tarjan算法的基本思路:
      1.任选一个点为根节点,从根节点开始。
      2.遍历该点u所有子节点v,并标记这些子节点v已被访问过。
      3.若是v还有子节点,返回2,否则下一步。
      4.合并v到u上。
      5.寻找与当前点u有询问关系的点v。
      6.若是v已经被访问过了,则可以确认u和v的最近公共祖先为v被合并到的父亲节点a。
    遍历的话需要用到dfs来遍历至于合并,最优化的方式就是利用并查集来合并两个节点。
伪代码:

Tarjan(u)//marge和find为并查集合并函数和查找函数
{
    for each(u,v)    //访问所有u子节点v
    {
        Tarjan(v);        //继续往下遍历
        marge(u,v);    //合并v到u上
        标记v被访问过;
    }
    for each(u,e)    //访问所有和u有询问关系的e
    {
        如果e被访问过;
        u,e的最近公共祖先为find(e);
    }
}

转载:https://blog.csdn.net/y990041769/article/details/40887469
ST算法:
这个算法是基于RMQ(区间最大最小值编号)的,不懂的可以这里学习一些
而求LCA就是把树通过深搜得到一个序列,然后转化为求区间的最小编号。
比如说给出这样一棵树。
这里写图片描述

我们通过深搜可以得到这样一个序列:

节点node:1 3 1 2 5 7 5 6 5 2 4 2 1 (先右后左)
深度depth: 1 2 1 2 3 4 3 4 3 2 3 2 1
首位first: 1 4 2 11 5 8 6

那么我们就可以这样写深搜函数

void dfs(int u,int deep)
{
    vis[u] = true;node[++tot] = u;first[u] = tot;depth[tot] = deep;
    int len = ve[u].size();
    for(int i = 0;i < len;++i)
    {
        int to = ve[u][i].fi;
        int value = ve[u][i].se;
        if(!vis[to]){
            dis[to] = dis[u] + value;
            dfs(to,deep + 1);
            node[++tot] = u;
            depth[tot] = deep;
        }
    }
}

搜索得到序列之后假如我们想求4 和 7的 LCA
那么我们找4和7在序列中的位置通过first 数组查找发现在6—11
即7 5 6 5 2 4 在上面图上找发现正好是以2为根的子树。而我们只要找到其中一个深度最小的编号就可以了、
这时候我们就用到了RMQ算法。
维护一个dp数组保存其区间深度最小的下标,查找的时候返回就可以了。
比如上面我们找到深度最小的为2点,返回其编号10即可。
st表:https://blog.csdn.net/qq_36386435/article/details/81710309
这部分不会的可以根据上面链接研究一些RMQ

代码可以这样写:

void ST()
{
    for(int i = 1;i <= n;++i)
    {
        dp[i][0] = i;
    }
    for(int j = 1;(1 << j) <= n;++j)
    {
        for(int i = 1;i + (1 << j) - 1 <= n;++i)
        {
            int a = dp[i][j - 1],b = dp[i + (1 << (j - 1))][j - 1];
            dp[i][j] = depth[a] < depth[b] ? a : b;
        }
    }
}

int RMQ(int l,int r)
{
    int k=(int)(log(double(r-l+1))/log(2.0));
    int a = dp[l][k],b = dp[r - (1 << k) + 1][k];
    return depth[a] < depth[b] ? a : b;
}

int LCA(int u,int v)
{
    int x = first[u],y = first[v];
    if(x > y) swap(x,y);
    int res = RMQ(x,y);
    return node[res];
}

例题解析:
hdu2586
How far away ?
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 23928 Accepted Submission(s): 9542

Problem Description
There are n houses in the village and some bidirectional roads connecting them. Every day peole always like to ask like this “How far is it if I want to go from house A to house B”? Usually it hard to answer. But luckily int this village the answer is always unique, since the roads are built in the way that there is a unique simple path(“simple” means you can’t visit a place twice) between every two houses. Yout task is to answer all these curious people.

Input
First line is a single integer T(T<=10), indicating the number of test cases.
For each test case,in the first line there are two numbers n(2<=n<=40000) and m (1<=m<=200),the number of houses and the number of queries. The following n-1 lines each consisting three numbers i,j,k, separated bu a single space, meaning that there is a road connecting house i and house j,with length k(0

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int inf = 0x3f3f3f3f;
const int N = 40005;

#define pb push_back
#define mp make_pair
#define fi first
#define se second

vector<pair<int,int> >ve[N];
int node[2 * N];
int depth[2 * N];
int first[2 * N];
int dis[2 * N];
bool vis[N];
int tot = 0;
int dp[N][50];
int n,m;

void dfs(int u,int deep)
{
    vis[u] = true;node[++tot] = u;first[u] = tot;depth[tot] = deep;
    int len = ve[u].size();
    for(int i = 0;i < len;++i)
    {
        int to = ve[u][i].fi;
        int value = ve[u][i].se;
        if(!vis[to]){
            dis[to] = dis[u] + value;
            dfs(to,deep + 1);
            node[++tot] = u;
            depth[tot] = deep;
        }
    }
}

void ST()
{
    for(int i = 1;i <= n;++i)
    {
        dp[i][0] = i;
    }
    for(int j = 1;(1 << j) <= n;++j)
    {
        for(int i = 1;i + (1 << j) - 1 <= n;++i)
        {
            int a = dp[i][j - 1],b = dp[i + (1 << (j - 1))][j - 1];
            dp[i][j] = depth[a] < depth[b] ? a : b;
        }
    }
}

int RMQ(int l,int r)
{
    int k=(int)(log(double(r-l+1))/log(2.0));
    int a = dp[l][k],b = dp[r - (1 << k) + 1][k];
    return depth[a] < depth[b] ? a : b;
}

int LCA(int u,int v)
{
    int x = first[u],y = first[v];
    if(x > y) swap(x,y);
    int res = RMQ(x,y);
    return node[res];
}

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        for(int i = 1;i <= n;++i)
            ve[i].clear();
        scanf("%d %d",&n,&m);
        for(int i = 0;i < n - 1;++i)
        {
            int a,b,c;
            scanf("%d %d %d",&a,&b,&c);
            ve[a].pb(mp(b,c));
            ve[b].pb(mp(a,c));
        }
        memset(vis,false,sizeof(vis));
        memset(dis,0,sizeof(dis));
        tot = 0;
        dfs(1,1);
        ST();
        //cout << 0 << endl;
//        for(int i = 1;i <= n;++i)
//        {
//            printf("%d ",dis[i]);
//        }
//        printf("\n");
        for(int i = 0;i < m;++i)
        {
            int a,b;
            scanf("%d %d",&a,&b);
            int res = dis[a] + dis[b] - 2 * dis[LCA(a,b)];
            printf("%d\n",res);
        }
    }
    return 0;
}

离线算法:

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int inf = 0x3f3f3f3f;
const int N = 40005;

#define pb push_back
#define mp make_pair
#define fi first
#define se second

int b[N];
vector<pair<int,int> >ve[N];
int dis[N];
bool vis[N];
int n,m;

typedef struct Query{
    int to,next,id;
}Query;
Query query[810];
int head[N];
int res[N];
int cnt = 0;

void addquery(int u,int v,int id)
{
    query[cnt].id = id;
    query[cnt].to = v;
    query[cnt].next = head[u];
    head[u] = cnt++;
}

int Find(int x)
{
    int i = x;
    while(x != b[x]){
        x = b[x];
    }
    while(i != b[i]){
        int t = b[i];
        b[i] = x;
        i = t;
    }
    return x;
}

void dfs(int u)
{
    int len = ve[u].size();
    vis[u] = true;
    //cout << u << endl;
    for(int i = 0;i < len;++i)
    {
        int to = ve[u][i].fi;
        int value = ve[u][i].se;
        if(!vis[to]){
            dis[to] = dis[u] + value;
            dfs(to);
            b[to] = u;
        }
    }
    for(int i = head[u];i != -1;i = query[i].next)
    {
        //cout << i << endl;
        int to = query[i].to;
        if(vis[to]){
            int x = Find(to);
            res[query[i].id] = dis[u] + dis[to] - 2 * dis[x];
        }
    }
}

int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        for(int i = 1;i <= n;++i)
            ve[i].clear();
        scanf("%d %d",&n,&m);
        for(int i = 0;i < n - 1;++i)
        {
            int x,y,z;
            scanf("%d %d %d",&x,&y,&z);
            ve[x].pb(mp(y,z));
            ve[y].pb(mp(x,z));
        }
        memset(vis,false,sizeof(vis));
        memset(dis,0,sizeof(dis));
        memset(head,-1,sizeof(head));
        for(int i = 1;i <= n;++i)
            b[i] = i;
        cnt = 0;
        for(int i = 0;i < m;++i)
        {
            int x,y;
            scanf("%d %d",&x,&y);
            addquery(x,y,i);
            addquery(y,x,i);
        }
        //cout << 0 << endl;
        dfs(1);
        //cout << 1 << endl;
        for(int i = 0;i < m;++i)
        {
            printf("%d\n",res[i]);
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_36386435/article/details/82634213