LCA 最近公共父祖先 POJ3379模板题

LCA(Least Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先。 ———来自百度百科

 

  • 暴力算法

以 1717 和 1818 为例,既然要求LCA,那么我们就让他们一个一个向上爬~~(我要一步一步往上爬 —— 《蜗牛》)~~,直到相遇为止。第一次相遇即是他们的LCA。 模拟一下就是:
17->14->10->7->317>14>10>7>3
18->16->12->8->5->318>16>12>8>5>3
最终结果就是 33
当然这个算法妥妥的会T飞掉,那么我们就要进行优化,于是就有了用倍增来加速的倍增LCA,这也是我们今天介绍的重点。

  • 倍增算法

所谓倍增,就是按22的倍数来增大,也就是跳 1,2,4,8,16,321,2,4,8,16,32 …… 不过在这我们不是按从小到大跳,而是从大向小跳,即按……32,16,8,4,2,132,16,8,4,2,1来跳,如果大的跳不过去,再把它调小。这是因为从小开始跳,可能会出现“悔棋”的现象。拿 55 为例,从小向大跳,5≠1+2+45=1+2+4,所以我们还要回溯一步,然后才能得出5=1+45=1+4;而从大向小跳,直接可以得出5=4+5=4+1。这也可以拿二进制为例,5(101)5(101),从高位向低位填很简单,如果填了这位之后比原数大了,那我就不填,这个过程是很好操作的。

还是以 1717 和 1818 为例,如果分别从1717和1818跳到33的话,它们的路径分别是*(此例只演示倍增,并不是倍增LCA算法的真正路径)*:
17->317>3
18->5->318>5>3

可以看出向上跳的次数大大减小。这个算法的时间复杂度为O(nlogn)O(nlogn),已经可以满足大部分的需求。

 

 

package pro.proclass;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.StringTokenizer;

public class P3379two {
    private static int N, M, S, level, cnt;
//    private static ArrayList<Integer>[] arr;
    private static int[][] parent;
    private static int[] depth, head;
    private static Edge[] edges;
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());
        S = Integer.parseInt(st.nextToken());

//        arr = new ArrayList[N+1];
        for (int i = 0; i <= N; i++) {
//            arr[i] = new ArrayList<>();
        }

        //1.parent 2. depth  3.lca
        // 求倍增法建立的树的最大跳的2^次方的值
        int num = 1;
        level = 0;
        while (num < N) {
            num+=num;
            level++;
        }

        edges = new Edge[2*(N-1)];
        parent = new int[level+1][N+1];
        depth = new int[N+1];
        cnt = 0;
        head = new int[N+1];
        Arrays.fill(head, -1);

        for (int i = 1; i < N; i++) {
            st = new StringTokenizer(br.readLine());
            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());
//            arr[a].add(b);
//            arr[b].add(a);
            add(a, b);
            add(b, a);
        }

        // 求深度和初始化parent[0][i];
        bfs();
        // 构建parent
        init();

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < M; i++) {
            st = new StringTokenizer(br.readLine());
            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());
            int ans = lca(a, b);

            sb.append(ans+"\n");
        }
        System.out.println(sb);
    }

    private static int lca(int a, int b) {
        if(depth[a] < depth[b]){
            a = a ^ b;
            b = a ^ b;
            a = a ^ b;
        }

        // 先进行跳跃深度大的点
        // 逆序的原因是,保证进行最大跨步的跳跃
        for (int i = level; i >= 0; i--) {
            // 如果跳跃了某个步子,深度依然大于b的的深度,那么继续跳
            if(depth[parent[i][a]] >= depth[b]) {
                a = parent[i][a];
            }
        }
        // 到这里两个点的深度必然一样,如果是同一个点 则返回
        if(a == b) return a;

        // 两个点同时往上跳,当跳跃的步子大,绝对是一个父节点,
        // 当刚刚不是一个父节点的时候进行跳跃
        for (int i = level; i >= 0; i--) {
            if(parent[i][a] != parent[i][b]) {
                a = parent[i][a];
                b = parent[i][b];
            }
        }

        return parent[0][a];
    }

    private static void init() {
        // 知道了0层的值,可以推导除其他层的值
        for (int i = 1; i <= level; i++) {
            for (int j = 1; j <= N; j++) {
                parent[i][j] = parent[i-1][parent[i-1][j]];
            }
        }

        // 推导方法的由来原因 2^n = (2 ^ n-1) + (2 ^ n-1)
    }

    private static void bfs() {
        LinkedList<Integer> que = new LinkedList<>();
        que.add(S);
        depth[S] = 1;
        int size = 0, step = 1;
        while (!que.isEmpty()) {
            size = que.size();
            step++;

            for (int i = 0; i < size; i++) {
                Integer cur = que.poll();

                for (int j = head[cur]; j != -1 ; j=edges[j].next) {
                    int next = edges[j].to;
//                for (int j = 0; j < arr[cur].size(); j++) {
//                    Integer next = arr[cur].get(j);
                    if(depth[next] == 0) {
                        depth[next] = step;
                        parent[0][next] = cur;
                        que.add(next);
                    }
                }
            }
        }
    }

    private static void add(int u, int v) {
        edges[cnt] = new Edge(v, head[u]);
        head[u] = cnt++;
    }

    static class Edge{
        int id;
        int to;
        int next;

        public Edge(int to, int next) {
            this.to = to;
            this.next = next;
        }
        public Edge(int to, int next, int id) {
            this.id = id;
            this.to = to;
            this.next = next;
        }
    }
}

 

posted @ 2021-06-08 16:47  姓蜀名黍  阅读(74)  评论(0)    收藏  举报