最近公共祖先(LCA)问题

  • 描述

对于有根树T的两个节点u和v,最近公共祖先LCA(T,u,v)表示一个节点x满足x是u,v的公共祖先且x的深度尽可能大。

  • 算法

求解LCA问题主要有三种解法,分别是暴力搜索,Tanjar算法,最后一种是转化为RMQ问题,用DFS+ST算法来求解

    • 暴力搜索

如果数据量不大的时候可以采用暴力搜索法。先将节点u的祖先节点全部标记出来,然后顺着节点v沿着父亲节点的方向向上遍历,直到遍历到一个被标记的节点,这个节点即为所求节点。或者分别获取u,v到根节点的路径P1,P2,可以将这两条路径看做两个两个链表,问题即转化为求两个无环链表的交点。

eg hihocder 1062

#include<bits/stdc++.h>
using namespace std;

map<string,string> maps;
set<string> fas;

int main(){
    int n;
    string str1,str2;
    cin>>n;
    for(int i=0; i<n; i++){
        cin>>str1>>str2;
        maps[str2] = str1;
    }
    cin>>n;
    while(n--){
        cin>>str1>>str2;
        fas.clear();
        fas.insert(str1);
        while(true){
            if(maps.find(str1) == maps.end())
                break;
            fas.insert(maps[str1]);
            str1 = maps[str1];
        }

        int flag = 0;
        while(true){
            if(fas.find(str2) != fas.end()){
                flag = 1;
                break;
            }
            if(maps.find(str2) == maps.end())
                break;
            str2 = maps[str2];
        }
        if(flag == 1)
            cout<<str2<<endl;
        else cout<<-1<<endl;
    }
    return 0;
}
    • Tanjar算法

Tanjar算法是一种离线算法,所谓离线算法是指先读入所有查询之后,再统一处理,得到所有结果。Tanjar算法的基础是:设t是u,v的公共祖先之一,且u和v分别位于t的两颗不同的子树,则t一定是u,v的最近公共祖先。所以,Tanjar算法的基本流程是:从根节点开始,采用深度优先搜索的方法遍历整颗树。对于当前节点t,设置t的祖先节点设为t,再对该节点的每颗子树进行遍历,每搜索完一颗子树的时候即可处理完子树内部的查询,再把子节点的祖先节点设为t。当所有子树遍历完成的时候,再处理关于t结点的LCA询问v,如果v已经被访问过,由于进行的是DFS,此时t,v的公共祖先一定还没确定,而且这个公共祖先就是v当前的祖先节点。算法为代码如下:

Tanjar(u){
    设置u的祖先节点为u
    对于u的孩子节点v:
          Tanjar(v)
          Union(u,v)
    对于关于u的查询节点v:
         如果v已经被访问过,则LCA(u,v) = v的祖先节点
    将u以及所有子节点的祖先节点设为u的父亲
}

  eg hihocoder1067

#include<iostream>
#include<map>
#include<cstring>
#include<vector>
using namespace std;

const int maxm = 100010;

int n,cnt;
int fa[maxm];
map<string,int> maps;
vector<vector<int> > graph;
vector<pair<int,int> > query[maxm];
string names[maxm];
int res[maxm];

int find(int pos){
    return pos==fa[pos] ? pos : fa[pos] = find(fa[pos]);
}

void solve(int node){
    fa[node] = node;
    int tmp;
    for(int i=0; i<graph[node].size(); i++){
        tmp  = graph[node][i];
        solve(tmp);
        fa[tmp] = node;
    }

    for(int i=0; i<query[node].size(); i++){
        pair<int,int> ps = query[node][i];
        if(fa[ps.first] != -1)
            res[ps.second] = find(ps.first);
    }
}

int main(){
    cin>>n;
    string str1,str2;
    int tp1,tp2;

    memset(fa,-1,sizeof(fa));

    cnt = 0;
    for(int i=0; i<n; i++){
        cin>>str1>>str2;
        if(maps.find(str1) == maps.end()){
            names[cnt] = str1;
            maps[str1] = cnt++;
            graph.push_back(vector<int>());
        }
        if(maps.find(str2) == maps.end()){
            names[cnt] = str2;
            maps[str2] = cnt++;
            graph.push_back(vector<int>());
        }
        graph[maps[str1]].push_back(maps[str2]);
    }
    cin>>n;
    int sp = 0;
    for(int i=0; i<n; i++){
        cin>>str1>>str2;
        tp1 = maps[str1];
        tp2 = maps[str2];
        query[tp1].push_back(make_pair(tp2,sp));
        query[tp2].push_back(make_pair(tp1,sp));
        sp++;
    }
    solve(0);
    int ps = 0;
    while(sp--){
        cout<<names[res[ps++]]<<endl;
    } 
    return 0;
}
    • RMQ(在线算法)

   最后一种解法是转化为RMQ问题,用DFS+ST算法的方式来求解。这种解法的基本思想是:如果将树看成无向图,则u,v的最近公共祖先一定在u,v的最短路径之上。该解法的思路为:

  1. DFS。从树T的根开始,进行DFS,并记录下每次到达的顶点,每经过一条边都记录它的端点,由于每条边恰好经过2次,因此一共记录了2n-1个结点,用E[1, ... , 2n-1]来表示,E中存储的为节点编号
  2. 计算R。再遍历的过程中,记录每一个节点第一次被遍历到的次序,用R来表示,即R[i]表示节点E中出现i的最小下标
  3. 求解。如果R[u] < R[v],则u,v之间的最短路径为E[R[u]],E[R[u]+1]...E[R[v]],则该路径中深度最小的肯定是u,v的最小公共祖先。R[u]>R[v]的时候同理。 
  4. 通过RMQ求解深度最小的节点。通过数组L来表示E中每一个节点对应的深度,所以数组L再DFS遍历的时候也可以求得。查找u,v的LCA的时,只需查找L[R[u]],L[R[u]+1]...L[R[v]]折一段中深度最小的元素的下标即可,通过该下标即可从E数组中得到对应的节点

eg hihocoder 1069

#include<iostream>
#include<map>
#include<cstring>
#include<vector>
using namespace std;

const int MAXM = 100010;
const int LEN = 20;

int cnt,cnt1;
map<string,int> maps;
string name[MAXM];
vector<int> graph[MAXM];
vector<pair<int,int> > query[MAXM];
int H[MAXM],L[2*MAXM],list[2*MAXM];
int dp[MAXM][LEN];

void dfs(int pos,int deep){
    if(H[pos] == -1){
        H[pos] = cnt1;
    }
    L[cnt1] = deep;
    list[cnt1++] = pos;
    for(int i=0; i<graph[pos].size(); i++){
        int sp = graph[pos][i];
        dfs(sp,deep+1);
        L[cnt1] = deep;
        list[cnt1++] = pos;
    }
}

void init(){
    memset(H,-1,sizeof(H));
    dfs(0,0);
    for(int i=0; i<cnt1; i++)
        dp[i][0] = i;
    for(int j=1; j<LEN; j++){
        int lens = 1<<j;
        for(int i=0; i<cnt1; i++){
            if(i+lens >= cnt1) break;
            int sp1 = dp[i][j-1];
            int sp2 = dp[i+(1<<(j-1))][j-1];
            if(L[sp1] < L[sp2]){
                dp[i][j] = sp1;
            }else dp[i][j] = sp2;
        }
    }
}

int querys(int pos1, int pos2){
    if(pos2 < pos1) swap(pos1,pos2);
    int len = pos2-pos1+1;
    int j;
    for(j=0; (1<<j)<=len; j++);
    j--;
    if((1<<j) == len) return dp[pos1][j];
    int sp1 = dp[pos1][j];
    int sp2 = querys(pos1+(1<<j),pos2);
    if(L[sp1] < L[sp2]) return sp1;
    return sp2;
}

int main(){
    int n,m;
    cin>>n;
    cnt = 0;
    cnt1 = 0;
    string str1,str2;

    while(n--){
        cin>>str1>>str2;
        if(maps.find(str1)==maps.end()){ maps[str1]=cnt; name[cnt]=str1; cnt++;}
        if(maps.find(str2)==maps.end()){ maps[str2]=cnt; name[cnt]=str2; cnt++;}
        int ps1 = maps[str1];
        int ps2 = maps[str2];
        graph[ps1].push_back(ps2);
    }
    init();

    cin>>m;
    while(m--){
        cin>>str1>>str2;
        int ps1 = maps[str1];
        int ps2 = maps[str2];
        int sp = querys(H[ps1],H[ps2]);
        cout<<name[list[sp]]<<endl;
    }

    return 0;
}

  

原文地址:https://www.cnblogs.com/leeshine/p/5789412.html