最近公共祖先lca基础 + 倍增进阶

14 篇文章 0 订阅
8 篇文章 0 订阅

先给个模板练习题(毒瘤bushi):P3379 【模板】最近公共祖先(LCA) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P3379

对于有根树T的两个结点u、v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u和v的祖先且x的深度尽可能大。在这里,一个节点也可以是它自己的祖先。 通俗来讲,就是两个点的公共祖先中,离他们最近的那一个。

求解公共祖先分两步:

第一步求出每一个结点的父结点:

public static void dfs(int now,int fath){
    fa[now] = fath;                        //记录父节点
    depth[now] = depth[fath] + 1;          //记录当前结点的深度:父节点+1
    for(int i=he[now];i>0;i=to[i]) {       //链式前向星遍历
        if(vv[i] == fath) continue;        //如果下一个结点是父亲,continue,不然死循环
        dfs(vv[i],now);                    //进入下一个结点
    }
}

第二步求两个点的最近公共祖先:

//这段代码很好理解
public static int lca(int x,int y){
   while(x != y) {
       if(depth[x] >= depth[y])
           x = fa[x];
       else
           y = fa[y];
   }
   return x;
}

用上面这个朴素方法的完整代码:

import java.io.*;
import java.util.*;

public class Main {
    static int maxn=500001; // also maxm
    static int n,m,s,cnt;
    static int[] vv=new int[maxn<<1],to=new int[maxn<<1],he=new int[maxn];
    static int[] depth=new int[maxn],fa = new int[maxn];
    public static void main(String[] args) throws IOException {
        n=nextInt();m=nextInt();s=nextInt();
        for(int i=1;i<n;i++){
            int x=nextInt(),y=nextInt();
            addEdge(x,y);
            addEdge(y,x);
        }
        dfs(s,0);
        for(int i=1;i<=m;i++){
            int ans = lca(nextInt(), nextInt());
            out.println(ans);
        }
        out.flush();
    }
    public static void dfs(int now,int fath){
        fa[now] = fath;
        depth[now] = depth[fath] + 1;
        for(int i=he[now];i>0;i=to[i]) {
            if(vv[i] == fath) continue;
            dfs(vv[i],now);
        }
    }

    public static int lca(int x,int y){
       while(x != y) {
           if(depth[x] >= depth[y])
               x = fa[x];
           else
               y = fa[y];
       }
       return x;
    }


    public static void addEdge(int u,int v){
        vv[++cnt]=v;
        to[cnt]=he[u];
        he[u]=cnt;
    }
    static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    static PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));

    static int nextInt() throws IOException {
        in.nextToken();
        return (int)in.nval;
    }
}

提交后发现AC了,但没完全AC。

 这是因为有毒瘤数据

499991 499993 416512 (n=499991, m=499993, s=416512)
314198 370928
125536 16538
277186 393869
171949 454871
160874 411546 ......

好家伙,这么大的数据,这一组数据就是故意来卡暴力求解的,看来我们得在基础模板上加一些优化,这时候倍增优化就来了。

我们先预处理求解log_{2}n+1,模拟下面这段代码可以得到log_{2}1=1, log_{2}2=2, log_{2}3=2, log_{2}4=3,也就是说,lg[ i ] - 1 = log_{2}i,这个lg[ i ]在倍增中可以用来确定高度和索引之间的关系。我们定义int[][] fa = new int[maxn][maxn] , 不同于基础模板的int[] fa = new int[maxn],倍增优化使用二维数组,多出来的一维存放2^{0},2^{1},2^{2}...2^{n}...,以便lca的时候可以利用倍增快速找祖宗。

for(int i=1;i<=n;i++)
    lg[i]=lg[i-1]+(1<<lg[i-1]==i?1:0);

还有个地方理解起来比较难:

for(int i=1;i<=lg[depth[now]];i++)           
    fa[now][i]=fa[fa[now][i-1]][i-1]; 

fa[now][i]=fa[fa[now][i-1]][i-1]可以理解为:now结点的第 2^{i} 的父亲 相当于 第2^{i-1}个父亲的第2^{i-1}的父亲,即2^{i}=2^{i-1}*2^{i-1}

上模板:

import java.io.*;
import java.util.*;
 
public class Main {
    static int maxn=500001; // n和m的可能最大值
    static int n,m,s,cnt;
    static int[] vv=new int[maxn<<1],to=new int[maxn<<1],he=new int[maxn<<1];
    static int[] lg=new int[maxn],depth=new int[maxn];
    static int[][] fa = new int[maxn][22];
    public static void main(String[] args) throws IOException {
        n=nextInt();m=nextInt();s=nextInt();
        for(int i=1;i<=n;i++)
            lg[i]=lg[i-1]+(1<<lg[i-1]==i?1:0);
        for(int i=1;i<n;i++){
            int x=nextInt(),y=nextInt();
            addEdge(x,y);
            addEdge(y,x);
        }
        dfs(s,0);
        for(int i=1;i<=m;i++){
            int x=nextInt(),y=nextInt();
            int ans=lca(x,y);
            out.println(ans);
        }
        out.flush();
    }

    public static void dfs(int now,int fath){
        fa[now][0]=fath;depth[now]=depth[fath]+1;    //这一行和基础模板大差不差
        for(int i=1;i<=lg[depth[now]];i++)           //解释过了
            fa[now][i]=fa[fa[now][i-1]][i-1];        
        for(int i=he[now];i>0;i=to[i])
            if(vv[i]!=fath) dfs(vv[i],now);
    }
 
    public static int lca(int x,int y){
        if(depth[x]<depth[y]) {int tmp=x;x=y;y=tmp;}    //保证x的深度大于或等于y的深度
        while(depth[x]>depth[y])
            x=fa[x][lg[depth[x]-depth[y]]-1];           //让x和y的深度一样
        if(x==y) return x;                             
        for(int k=lg[depth[x]]-1;k>=0;k--)  //这里x和y已经在同一个深度,那么一起网上倍增跳
            if(fa[x][k]!=fa[y][k]){         //也可以说指数爆炸式地跳
                x=fa[x][k];y=fa[y][k];
            }
        return fa[x][0];
    }
 
 
    //使用链式前向星
    public static void addEdge(int u,int v){
        vv[++cnt]=v;
        to[cnt]=he[u];
        he[u]=cnt;
    }

    //魔法快读
    static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
    static PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
 
    static int nextInt() throws IOException {
        in.nextToken();
        return (int)in.nval;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

玛卡左家陇分卡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值