【算法】【LCA】【tarjan】【倍增】

呃,这个常用但是我一直不会

Tarjan

  Tarjan 算法基于 dfs ,在 dfs 的过程中,对于每个节点位置的询问做出相应的回答。

  dfs 的过程中,当一棵子树被搜索完成之后,就把他和他的父亲合并成同一集合;在搜索当前子树节点的询问时,如果该询问的另一个节点已经被访问过,那么该编号的询问是被标记了的,于是直接输出当前状态下,另一个节点所在的并查集的祖先;如果另一个节点还没有被访问过,那么就做下标记,继续 dfs 。

//tarjian,边建边 边回答问题
#include<cstdio>
#include<cstdlib>
#include<vector>
using namespace std;
int n,m,rt;
const int N=500003,M=500003;
int ans[M];
vector <int> e[N];
struct node
{
    int v,id;
    node(int vv,int ii)
    { v=vv,id=ii; }
    node(){}
};
vector <node> q[N];

int fa[N];
int find(int x)
{ return fa[x]==0?x:fa[x]=find(fa[x]); }
bool vis[N];
void dfs(int x,int f)
{
    int sz=e[x].size();
    for(int i=0;i<sz;i++)
    {
        int v=e[x][i];
        if(v!=f) dfs(v,x),fa[v]=x;
    }
    vis[x]=true;
    
    sz=q[x].size();
    for(int i=0;i<sz;i++)
    {
        int v=q[x][i].v ;
        if(!ans[q[x][i].id ] && vis[v])
            ans[q[x][i].id ]=find(v);
    }
}

int main()
{
    scanf("%d%d%d",&n,&m,&rt);
    int u,v;
    for(int i=1;i<n;i++) 
        scanf("%d%d",&u,&v),e[u].push_back(v),e[v].push_back(u);
    for(int i=0;i<m;i++)
        scanf("%d%d",&u,&v),q[u].push_back(node(v,i)),q[v].push_back(node(u,i));
    
    dfs(rt,0);
    
    for(int i=0;i<m;i++) printf("%d\n",ans[i]);
    
    return 0;
} 

倍增

就从父亲更新到儿子

#include<cstdio>
#include<cstdlib>
#include<vector>
#include<algorithm>
using namespace std;
int n,q;
const int N=100003,Q=10003;
int fa[N][21],dep[N];//辅助数组dep 
vector <int > g[N]; 
bool tag[N];

void dfs(int x)
{
    dep[x]=dep[fa[x][0]]+1;
    for(int i=1;i<21;i++)
        fa[x][i]=fa[fa[x][i-1]][i-1];
        
    int sz=g[x].size() ;
    for(int i=0;i<sz;i++)
        dfs(g[x][i]);
}
int lg[N];
void get_log()
{
    for(int i=1;i<n;i++)
        lg[i]=lg[i-1]+(i==(1<<(lg[i-1])));//这里是看最高位,是第几位(第一位表示二进制中第0位) 
}
int LCA(int u,int v)
{
    if(dep[u]<dep[v]) swap(u,v); 
    int dis=dep[u]-dep[v];
    for(int i=1,j=0;dis;i<<=1,j++)
        if(dis&i)
        {
            dis^=i;
            u=fa[u][j];
        }
    if(u==v) return u;
    
    for(int i=lg[dep[u]]-1;i>=0;i--)
        if(fa[u][i]!=fa[v][i])
            u=fa[u][i],v=fa[v][i];
    return fa[u][0];
}

int main()
{
    scanf("%d",&n);
    int u,v;
    for(int i=1;i<n;i++)
        scanf("%d%d",&u,&v),fa[v][0]=u,g[u].push_back(v),tag[v]=true;
    for(int i=1;i<=n;i++)
        if(!tag[i])
        {
            dfs(i);
            break;
        }
    
    get_log();
    scanf("%d",&q); 
    while(q--)
    {
        scanf("%d%d",&u,&v);
        printf("%d\n",LCA(u,v));
    }
    
    return 0;
} 

 

#include<cstdio>#include<cstdlib>#include<vector>#include<algorithm>using namespace std;int n,q;const int N=100003,Q=10003;int fa[N][21],dep[N];//辅助数组dep vector <int > g[N]; bool tag[N];
void dfs(int x){dep[x]=dep[fa[x][0]]+1;for(int i=1;i<21;i++)fa[x][i]=fa[fa[x][i-1]][i-1];int sz=g[x].size() ;for(int i=0;i<sz;i++)dfs(g[x][i]);}int lg[N];void get_log(){for(int i=1;i<n;i++)lg[i]=lg[i-1]+(i==(1<<(lg[i-1])));//这里是看最高位,是第几位(第一位表示二进制中第0位) }int LCA(int u,int v){if(dep[u]<dep[v]) swap(u,v); int dis=dep[u]-dep[v];for(int i=1,j=0;dis;i<<=1,j++)if(dis&i){dis^=i;u=fa[u][j];}if(u==v) return u;for(int i=lg[dep[u]]-1;i>=0;i--)if(fa[u][i]!=fa[v][i])u=fa[u][i],v=fa[v][i];return fa[u][0];}
int main(){scanf("%d",&n);int u,v;for(int i=1;i<n;i++)scanf("%d%d",&u,&v),fa[v][0]=u,g[u].push_back(v),tag[v]=true;for(int i=1;i<=n;i++)if(!tag[i]){dfs(i);break;}get_log();scanf("%d",&q); while(q--){scanf("%d%d",&u,&v);printf("%d\n",LCA(u,v));}return 0;} 

转载于:https://www.cnblogs.com/xwww666666/p/11327012.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LCA(最近公共祖先)是指在一棵树中,找到两个节点的最近的共同祖先节点。而Tarjan算法是一种用于求解强连通分量的算法,通常应用于有向图中。它基于深度优先搜索(DFS)的思想,通过遍历图中的节点来构建强连通分量。Tarjan算法也可以用于求解LCA问题,在有向无环图(DAG)中。 具体来说,在使用Tarjan算法求解LCA时,我们需要进行两次DFS遍历。首先,我们从根节点开始,遍历每个节点,并记录每个节点的深度(即从根节点到该节点的路径长度)。然后,我们再进行一次DFS遍历,但这次我们在遍历的过程中,同时进行LCA的查找。对于每个查询,我们将两个待查询节点放入一个查询列表中,并在遍历过程中记录每个节点的祖先节点。 在遍历的过程中,我们会遇到以下几种情况: 1. 如果当前节点已被访问过,说明已经找到了该节点的祖先节点,我们可以更新该节点及其所有后代节点的祖先节点。 2. 如果当前节点未被访问过,我们将其标记为已访问,并将其加入到查询列表中。 3. 如果当前节点有子节点,我们继续递归遍历子节点。 最终,对于每个查询,我们可以通过查询列表中的两个节点的最近公共祖先节点来求解LCA。 需要注意的是,Tarjan算法的时间复杂度为O(V+E),其中V为节点数,E为边数。因此,对于大规模的树结构,Tarjan算法是一种高效的求解LCA问题的方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值