zoukankan      html  css  js  c++  java
  • 树的运用:求树上共同祖先LCA

    注:求解LCA有至少7种做法,如果让我全部写出来,我会死掉的。这里我只讲朴素和倍增

    注:抄别人的代码不是一个好习惯 我不会告诉你这个代码我可以弄了一点点失误进去

    版权声明:倍增代码使用的是李白莘莘学子的代码,原文点这里


    下面列举一下LCA的做法:朴素算法,倍增,RMQ,用欧拉序列转化为RMQ ,太监(tarjan)和动态树(看这里有惊喜
    RMQ以后将分块时可能会去提(前提我记得)(180+行),太监其中有dfs序和邻接链表,不讲(90+行)。树剖以后讲到会提(很快就讲)

    //朴素算法
    #include<bits/stdc++.h>
    #define MAXN 100100
    using namespace std
    int n,head[MAXN],dep[MAXN],cnt=0,q,fa[MAXN];
    struct edge
    {
    	int nxt,to;
    }e[MAXN];
    void add(int u,int v)
    {
    	e[++cnt].nxt=head[u];
    	e[cnt].to=v;
    	head[u]=cnt;
    }
    void dep_ccl(int u,int f)//预处理fa[]数组及深度
    {
    	dep[u]=dep[f]+1;//原点深度等于他爸的深度加一
    	fa[u]=f;
    	for(int i=head[u];i!=0;i=e[i].nxt)//基操遍历
    {
    		int v=e[i].to;
    		if(v!=f)dep_ccl(v,u);
    	}	
    }
    int main()
    {
    	scanf("%d",&n);
    	for(int i=1;i<n;i++)
    	{
    		int a,b;
    		scanf("%d%d",&a,&b);
    		add(a,b);
    		add(b,a);
    	}
    	dfs(1,0);
    	scanf("%d",&q);
    	for(int i=1;i<=q;i++)
    	{
    		ans=0;
    		int a,b;
    		scanf("%d%d",&a,&b);
    		while(a!=b)//LCA
    		{
    			if(dep[a]>=dep[b])a=fa[a];
    			else b=fa[b];
    		}
    		cout<<a<<endl;
    	}
    	return 0;
    }
    
    //倍增
    #include<include>//万能头 
    #define MAXN 200200
    using namespace std;
    int n,m,s,cnt=0,head[MAXN],dep[MAXN],f[MAXN][23];
    int a,a;
    struct edge{
        int next,to;
    }e[4*MAXN]
    void e_add(int u,int v)//链式前向星存图 
    {
        cnt++;
        e[cnt].next=head[u];e[cnt].to=v;head[u]=cnt;
        e[++cnt].next=head[v];e[cnt].to=u;head[v]=cnt;
    }
    void dfs(int u,int father)//对应深搜预处理f数组 
    {
        dep[u]=dep[father]+1;
        for(int i=1;(1<<i)<=dep[u];i++)//预处理f数组 
        {
            f[u][i]=f[f[u][i-1]][i-1];
        }
        for(int i=head[u];i;i=e[i].next)//遍历树 
        {
            int v=e[i].to;
            if(v==father)continue;//双向图需要判断是不是父亲节点 
            f[v][0]=u;
            dfs(v,u);
        }
    }
    int lca(int x,int y)
    {
        if(dep[x]<dep[y])swap(x,y);
        for(int i=20;i>=0;i--)//从大到小枚举使x和y到了同一层 
        {
            if(dep[f[x][i]]>=dep[y])x=f[x][i];
            if(x==y)return x;
        }
        for(int i=20;i>=0;i--)//从大到小枚举 
        {
            if(f[x][i]!=f[y][i])//尽可能接近 
            {
                x=f[x][i];y=f[y][i];
            } 
        } 
        return f[x][0];//f[y][0]也ok 
    }
    int main(){
        scanf("%d%d%d",&n,&m,&s);
        for(int i=1;i<n;i++)
        {
            scanf("%d",&a1);scanf("%d",&a2);
            e_add(a1,a2);//链式前向星存图
        }
        dfs(s,0);//预处理 
        for(int i=1;i<=m;i++)
        {
            scanf("%d %d",&a1,&a2);
            printf("%d
    ",lca(a1,a2));//求两个节点的LCA 
        }
    } 
    

    首先,是我们的朴素算法O(n^2)。
    首先,将树上每个节点的深度预处理出来,还有他们的霸霸预处理出来。
    方法:爆搜。开两个函数变量:u(遍历到的节点)和f(u他爸)。将整棵树遍历一遍。
    遍历过程中,将fa[u](即u他的fuqin节点)赋值为f,dep[u](即u的深度)赋值为dep[f]+1(即u他爸的深度,以前遍历出来过)


    然后在主函数中,定义两个指针a,b,最开始的时候指向要求LCA的那两个点。
    每次让更深的那个指针往上跳一个点。即a=fa[a]||b=fa[b]
    然后在两指针指向同一个点时,这个点就是他们的LCA,原因你自己想想。这不是废话吗


    倍增,就是在朴素算法的基础上,呈倍 增上去。同样,有一个fa数组,需要预处理出来。
    其中fa[i][j]表示i的第(2^j)个祖先。利用fa的定义(即fa[i][j]表示i的第(2^j)个祖先)用循环预处理出fa数组。
    其中有个地方做一点点解释

    for(int i=20;i>=0;i--)//从大到小枚举使x和y到了同一层 
        {
            if(dep[f[x][i]]>=dep[y])x=f[x][i];
            if(x==y)return x;
        }
        for(int i=20;i>=0;i--)//从大到小枚举 
        {
            if(f[x][i]!=f[y][i])//尽可能接近 
            {
                x=f[x][i];y=f[y][i];
            } 
        } 
    

    这里i为什么是20到1呢?先看代码
    我们发现f的第二位的下标都是i,意味着一定是求某个数的(2^i)个祖先。
    如果你担心,你尽可以开31->1甚至63->1,不过一般题目不会给那么大的数据,一般是2^20左右的,这就是这样来的。


  • 相关阅读:
    Redis 实现队列优先级
    Redis 实现安全队列
    快速理解linux流编辑器sed命令
    Varnish 简介
    手机访问本地服务器
    javascript类的类比详解-大白话版
    优秀前端工程师应该掌握的内容(转自:github)
    mongodb初步使用
    Markdown 语法速查表
    pace.js和NProgress.js两个加载进度插件的一点小总结
  • 原文地址:https://www.cnblogs.com/riced/p/13780268.html
Copyright © 2011-2022 走看看