zoukankan      html  css  js  c++  java
  • 最近公共祖先(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;
    }
    

      

  • 相关阅读:
    poj2248
    poj2249
    poj2255
    电脑族每天必喝的四杯茶
    如何获得每一行的ROWID
    网上勾引MM定义大全
    A Forever Friend (永远的朋友)
    生活中的经典感人语句
    经理人必看的10个管理网站
    男人必须明白的22个道理
  • 原文地址:https://www.cnblogs.com/leeshine/p/5789412.html
Copyright © 2011-2022 走看看