zoukankan      html  css  js  c++  java
  • LCA-最近公共祖先的四种解法

    洛谷模板题 P3379

    给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

    ST表

    基本思路

    树的构造-举个栗子

    欧拉序

    存储第一次出现的位置

    使用链式前向星存储树的结构,首先求一棵树的欧拉序,然后根据两点的公共祖先就是欧拉序中(第一次出现时)两点位置区间内的深度最小点,
    这一定理转化成RMQ最小问题,使用ST表时注意log2的复杂度比较高,ST表总体速度较慢,约900ms

    #include<bits/stdc++.h>
    #define FOR(i,a,b) for(int i=a;i<b;i++)
    #define FOR2(i,a,b) for(int i=a;i<=b;i++)
    #define sync ios::sync_with_stdio(false);
    #define ll long long
    #define INF  0x7f7f7f7f;
    #define MAXN 1001000
    #define ok1 cout<<"OK1"<<endl
    #define ok2 cout<<"OK2"<<endl
    #define ok3 cout<<"OK3"<<endl
    #define MOD 10007
    using namespace std;
    int arr[MAXN][(int)log2(MAXN)+1],loc[MAXN][(int)log2(MAXN)+1],vis[MAXN/2],n,m,s,eu,cnt,ed=0,head[MAXN];
    typedef struct{
    	int from,to,next;
    }EDGE;EDGE edges[MAXN];
    inline int read()
    {
        char ch=getchar();
        int x=0,f=1;
        while((ch>'9'||ch<'0')&&ch!='-')
            ch=getchar();
        if(ch=='-')
        {
            f=-1;
            ch=getchar();
        }
        while('0'<=ch&&ch<='9')
        {
            x=x*10+ch-'0';
            ch=getchar();
        }
        return x*f;
    }
    inline void put(int x)
    {
        if(x==0)
        {
            putchar('0');
            putchar('
    ');
            return;
        }
        int num=0;
        char c[25];
        while(x)
        {
            c[++num]=(x%10)+48;
            x/=10;
        }
        while(num)
            putchar(c[num--]);
        putchar('
    ');
        return ;
    }
    inline int log2(int x)
    {
    	int k=0;while(x>1)x=x>>1,k++;return k;
    }
    inline void dfs(int pre,int cur,int level)
    {
    	arr[++cnt][0]=level;//深度欧拉序 
    	loc[++eu][0]=cur;//节点欧拉序 
    	if(vis[cur]==0)vis[cur]=eu;//位置 
    	for(int i=head[cur];i;i=edges[i].next)
    	{
    		if(edges[i].to==pre)continue;
    		dfs(cur,edges[i].to,level+1);
    		arr[++cnt][0]=level;
    	}
    	if(pre!=cur)
    	{
    		loc[++eu][0]=pre;
    	}
    }
    inline void add(int a,int b)
    {
    	edges[++ed].from=a;
    	edges[ed].to=b;
    	edges[ed].next=head[a];
    	head[a]=ed;
    	return;
    } 
    int main()
    {
    //	freopen("t1.in","r",stdin);
    //	freopen("t111.out","w",stdout);
    	scanf("%d%d%d",&n,&m,&s);
    	int a,b;
    	eu=cnt=0;
    	FOR(i,0,n-1)
    	{
    		scanf("%d%d",&a,&b);
    		add(a,b);add(b,a);
    	}
    	dfs(0,s,1);
    	
    	//rmq
    	int logh=log2(cnt);
    	for(int h=1;h<=logh;h++)
    	{
    		for(int i=1;i+(1<<h)-1<=cnt;i++)
    		{
    			if(arr[i][h-1]<arr[i+(1<<h-1)][h-1])
    			{//欧拉序对应的位置 
    				arr[i][h]=arr[i][h-1];
    				loc[i][h]=loc[i][h-1];
    			}
    			else{
    				arr[i][h]=arr[i+(1<<h-1)][h-1];
    				loc[i][h]=loc[i+(1<<h-1)][h-1];
    			}
    		}
    	}
    	FOR(i,0,m){
    		scanf("%d%d",&a,&b);
    		a=vis[a];b=vis[b];
    		if(a>b)swap(a,b);
    		int logk=log2(b-a+1);
    		if(arr[a][logk]<arr[b-(1<<logk)+1][logk]){
    			printf("%d
    ",loc[a][logk]); 
    		} 
    		else printf("%d
    ",loc[b-(1<<logk)+1][logk]);
    	}
    	return 0;
    }
    

    倍增

    基本思路

    先dfs处理树,记录每个节点上跳(2^{1 o {maxjump}})次能到达的祖先节点,

    然后读取要求的点(a,b),处理成同一深度后,若(b)(a)的祖先,则返回b,
    若不是,则同时上跳(2^{1 o{maxjump}})次,直到相等.

    时间比ST表快,约550ms。预处理时间复杂度O(nlogn),每次查询时间复杂度O(logn)

    参考

    https://blog.csdn.net/qq_42790311/article/details/81486742
    https://blog.csdn.net/txl199106/article/details/53998831

    #include<bits/stdc++.h>
    #define FOR(i,a,b) for(int i=a;i<b;i++)
    #define FOR2(i,a,b) for(int i=a;i<=b;i++)
    #define ll long long
    #define INF  0x7f7f7f7f;
    #define MAXN 1000100
    #define MOD 10007
    using namespace std;
    typedef struct{
    	int from,to,next;
    }EDGE;EDGE edges[MAXN];
    int maxjump=(int)log2(MAXN)+1,head[MAXN],ed=0,n,m,s,a,b,dep[MAXN],jump[MAXN][(int)log2(MAXN)+1];
    inline int read()
    {
        char ch=getchar();
        int x=0,f=1;
        while((ch>'9'||ch<'0')&&ch!='-')
            ch=getchar();
        if(ch=='-')
        {
            f=-1;
            ch=getchar();
        }
        while('0'<=ch&&ch<='9')
        {
            x=x*10+ch-'0';
            ch=getchar();
        }
        return x*f;
    }
    inline void put(int x)
    {
        if(x==0)
        {
            putchar('0');
            putchar('
    ');
            return;
        }
        int num=0;
        char c[25];
        while(x)
        {
            c[++num]=(x%10)+48;
            x/=10;
        }
        while(num)
            putchar(c[num--]);
        putchar('
    ');
        return ;
    }
    inline int log2(int x)
    {
    	int k=0;while(x>1)x=x>>1,k++;return k;
    }
    inline void add(int a,int b)
    {
    	edges[++ed].from=a;
    	edges[ed].to=b;
    	edges[ed].next=head[a];
    	head[a]=ed;
    	return;
    } 
    void dfs(int cur,int pre)
    {
    	jump[cur][0]=pre;
    	dep[cur]=dep[pre]+1;
    	for(int i=1;i<maxjump;i++)
    	{
    		jump[cur][i]=jump[jump[cur][i-1]][i-1];//预处理 
    	}
    	for(int i=head[cur];i;i=edges[i].next)
    	{
    		if(edges[i].to!=pre)
    			dfs(edges[i].to,cur);	
    	} 
    }
    int lca(int x,int y)
    {
    	if(dep[x]<dep[y])
    		swap(x,y);
    //	for(int i=maxjump-1;i>=0;i--)
    //		if((1<<i)<=dep[x]-dep[y])
    //			x=jump[x][i];//处理成同一深度 
    	while(dep[x]>dep[y])x=jump[x][] 
    	if(x==y)//y是x的祖先 
    		return x;
    	for(int i=maxjump-1;i>=0;i--)
    	{//一起倍增 
    		if(jump[x][i]!=jump[y][i])
    		{
    			x=jump[x][i],y=jump[y][i];
    		}
    	 } 
    	 return jump[x][0];//返回父节点 
    }
    int main()
    {
    	scanf("%d%d%d",&n,&m,&s);
    	FOR(i,0,n-1)
    	{
    		scanf("%d%d",&a,&b);
    		add(a,b);add(b,a);
    	}
    	dfs(s,0);
    	FOR(i,0,m)
    	{
    		scanf("%d%d",&a,&b);
    		printf("%d
    ",lca(a,b));	
    	} 
    	return 0;
    }
    

    Tarjan算法

    基本思路

    1. 先选择一个节点u为根节点,从根节点开始搜索。(标记u已访问过)
    2. 遍历该点u的所有儿子节点v,并标记v已访问过。
    3. 若v还有儿子节点,对v重复ii操作,否则进入下一操作。
    4. 把v合并到u上(并查集)。
    5. 把当前的点设为u,遍历与u有询问关系的节点v。
    6. 如果v在之前已经被访问过,那么u和v的最近公共祖先就是v通过并查集合并后的父亲节点(注意是合并后),即当前的find(v)。
      预处理时间复杂度O(nlogn),每次查询时间复杂度O(1),总时间复杂度是O(nlogn+q),约400ms
      Tarjan伪代码
    Tarjan(u)//marge和find为并查集合并函数和查找函数
    {
        for each(u,v)    //访问所有u子节点v
        {
            Tarjan(v);        //继续往下遍历
            marge(u,v);    //合并v到u上
            标记v被访问过;
        }
        for each(u,e)    //访问所有和u有询问关系的e
        {
            如果e被访问过;
            u,e的最近公共祖先为find(e);
        }
    }
    

    参考

    https://www.cnblogs.com/JVxie/p/4854719.html

    #include<bits/stdc++.h>
    #define FOR(i,a,b) for(int i=a;i<b;i++)
    #define FOR2(i,a,b) for(int i=a;i<=b;i++)
    #define ll long long
    #define INF  0x7f7f7f7f;
    #define MAXN 1000100
    #define MOD 10007
    using namespace std;
    typedef struct{
    	int from,to,next;
    }EDGE;EDGE edges[MAXN],vedges[MAXN];
    int head[MAXN],vhead[MAXN],ed=0,ved=0,n,m,s,a,b,fa[MAXN],vis[MAXN],lca[2*MAXN];
    inline int read()
    {
        char ch=getchar();
        int x=0,f=1;
        while((ch>'9'||ch<'0')&&ch!='-')
            ch=getchar();
        if(ch=='-')
        {
            f=-1;
            ch=getchar();
        }
        while('0'<=ch&&ch<='9')
        {
            x=x*10+ch-'0';
            ch=getchar();
        }
        return x*f;
    }
    inline void put(int x)
    {
        if(x==0)
        {
            putchar('0');
            putchar('
    ');
            return;
        }
        int num=0;
        char c[25];
        while(x)
        {
            c[++num]=(x%10)+48;
            x/=10;
        }
        while(num)
            putchar(c[num--]);
        putchar('
    ');
        return ;
    }
    inline int log2(int x)
    {
    	int k=0;while(x>1)x=x>>1,k++;return k;
    }
    inline void add(int a,int b)
    {
    	edges[++ed].from=a;
    	edges[ed].to=b;
    	edges[ed].next=head[a];
    	head[a]=ed;
    	return;
    } 
    
    inline void vadd(int a,int b)
    {
    	vedges[++ved].from=a;
    	vedges[ved].to=b;
    	vedges[ved].next=vhead[a];
    	vhead[a]=ved;
    	return ;
    }
    inline int find(int x)
    {
    	if(x!=fa[x]) fa[x]=find(fa[x]);
    	return fa[x];
    }
    void tarjan(int u)
    {
    	vis[u]=true;
    	for(int i=head[u];i;i=edges[i].next)
    	{
    		if(vis[edges[i].to])continue;//访问过则跳过 
    		tarjan(edges[i].to);
    		fa[edges[i].to]=u;//合并 
    	}
    	for(int i=vhead[u];i;i=vedges[i].next)
    	{
    		if(vis[vedges[i].to])
    		{
    			lca[i]=find(vedges[i].to);//寻找已经被访问过的点的祖先
    			if(i&1)//询问了两遍
    				lca[i+1]=lca[i];
    			else
    				lca[i-1]=lca[i]; 
    		}
    	}
    }
    int main()
    {
    	FOR(i,0,MAXN)fa[i]=i;
    	scanf("%d%d%d",&n,&m,&s);
    	FOR(i,0,n-1)
    	{//读入边 
    		scanf("%d%d",&a,&b);
    		add(a,b);add(b,a);
    	}
    	FOR(i,0,m)
    	{//离线查询 
    		scanf("%d%d",&a,&b);
    		vadd(a,b),vadd(b,a);
    	}
    	tarjan(s);
    	for(int i=1;i<=m;i++)
    		put(lca[i*2]);
    	return 0;
    }
    

    树链剖分

    约束RMQ

    参考

    https://www.cnblogs.com/ghostcai/p/9280720.html
    https://blog.csdn.net/qq_36909245/article/details/80055114

  • 相关阅读:
    Java's Volatile Keyword
    reflection
    Spring
    Stack
    Set
    Vector & ArrayList
    CreateFileDemo
    session原理
    multithreadingDemo
    REST风格
  • 原文地址:https://www.cnblogs.com/tldr/p/11291807.html
Copyright © 2011-2022 走看看