AcWing 算法提高课 最近公共祖先(LCA)

1、向上标记法

  从节点向上查找,O(n)

2、倍增法,预处理O(nlogn),查询O(qlogn)

  记录当前节点向上2^j 步的祖先是谁

  

  记录当前节点的深度depth[i]

  哨兵:节点0,且depth[0]=0。当fa[i][j]已经跳出这棵树时,fa[i][j]=0

  步骤:

  (1) 先将两个节点跳到同一层

  (2) 两个节点同时向上跳,直到跳到最近公共祖先的下一层

模板:

const int N=100010;
int n;
int root=-1;
vector<int> adj[N];
int depth[N];
int pa[N][20];//第二维和n的范围对应

//初始化
void BFS()
{
    memset(depth,0x3f,sizeof(depth));
    depth[0]=0;
    depth[root]=1;
    queue<int> que;
    que.push(root);
    while(que.size())
    {
        int u=que.front();
        que.pop();
        for(auto nxt:adj[u])
        {
            if(depth[nxt]>depth[u]+1)
            {
                depth[nxt]=depth[u]+1;
                que.push(nxt);
                pa[nxt][0]=u;
                fore(i,1,16)
                {
                    //预处理pa,倍增思想
                    pa[nxt][i]=pa[pa[nxt][i-1]][i-1];
                }
            }
        }
    }
}

int LCA(int a,int b)
{
    //交换,使a的深度>=b的深度
    if(depth[a]<depth[b]) swap(a,b);
    
    for(int i=16;i>=0;i--)
    {
        if(depth[pa[a][i]]>=depth[b])
        {
            a=pa[a][i];
        }
    }
    
    if(a==b) return a;
    for(int i=16;i>=0;i--)
    {
        if(pa[a][i]!=pa[b][i])
        {
            a=pa[a][i];
            b=pa[b][i];
        }
    }
    return pa[a][0];
}
void YD()
{
    cin>>n;
    ////输入////
    int a,b;
    fore(i,1,n)
    {
        cin>>a>>b;
        if(b==-1)
            root=a;
        else
        {
            adj[a].push_back(b);
            adj[b].push_back(a);
        }
    }
    ////输入////
    BFS();
    ////查询////
    int q;
    cin>>q;
    while(q--)
    {
        int x,y;
        cin>>x>>y;
        int p=LCA(x,y);
        if(p==x)
            cout<<1<<endl;
        else if(p==y)
            cout<<2<<endl;
        else
            cout<<0<<endl;
    }
    ////查询////
}
View Code

 3、Tarjan——离线求LCA,查询O(n+q)

 

int n,q;
const int N=10010;
vector<int> adj[N];
vector<int> cost[N];
vector<pair<int,int>> query[N];//v,qid
int res[2*N];
int dis[N];
int mark[N];
int p[N];
void DFS(int u,int pa)
{
    for(int i=0;i<adj[u].size();i++)
    {
        int nxt=adj[u][i];
        int c=cost[u][i];
        if(nxt!=pa)
        {
            dis[nxt]=dis[u]+c;
            DFS(nxt,u);
        }
    }
}

int find(int x)
{
    if(p[x]!=x) p[x]=find(p[x]);
    return p[x];
}
void Tarjan(int u)
{
    mark[u]=1;
    for(int i=0;i<adj[u].size();i++)
    {
        int nxt=adj[u][i];
        int c=cost[u][i];
        if(mark[nxt]==0)//子节点而非父节点
        {
            Tarjan(nxt);
            p[nxt]=u;
        }
    }
    for(auto [v,id]:query[u])
    {
        if(mark[v]==2)
        {
            int pa=find(v);
            res[id]=dis[u]+dis[v]-2*dis[pa];
        }
    }
    mark[u]=2;
}
void YD()
{
    cin>>n>>q;
    int a,b,c;
    fore(i,1,n-1)
    {
        cin>>a>>b>>c;
        adj[a].push_back(b);
        cost[a].push_back(c);
        adj[b].push_back(a);
        cost[b].push_back(c);
    }
    
    fore(i,1,q)
    {
        cin>>a>>b;
        query[a].push_back({b,i});
        query[b].push_back({a,i});
    }
    fore(i,1,n) p[i]=i;
    DFS(1,0);//get dis
    Tarjan(1);
    fore(i,1,q)
    {
        cout<<res[i]<<endl;
    }
}
View Code

利用并查集将绿色合并到其子树的根节点,然后显然可以求出与红色节点的LCA,其中,红色路线是当前的DFS栈。

注意先进行DFS子节点,当子节点DFS完成后,回到当前节点,再进行查询。

模板:

 

int n,q;
const int N=10010;
vector<int> adj[N];
vector<int> cost[N];
vector<pair<int,int>> query[N];//v,qid
int res[2*N];
int dis[N];
int mark[N];
int p[N];
void DFS(int u,int pa)
{
    for(int i=0;i<adj[u].size();i++)
    {
        int nxt=adj[u][i];
        int c=cost[u][i];
        if(nxt!=pa)
        {
            dis[nxt]=dis[u]+c;
            DFS(nxt,u);
        }
    }
}

int find(int x)
{
    if(p[x]!=x) p[x]=find(p[x]);
    return p[x];
}
void Tarjan(int u)
{
    mark[u]=1;
    for(int i=0;i<adj[u].size();i++)
    {
        int nxt=adj[u][i];
        int c=cost[u][i];
        if(mark[nxt]==0)//子节点而非父节点
        {
            Tarjan(nxt);
            p[nxt]=u;
        }
    }
    for(auto [v,id]:query[u])
    {
        if(mark[v]==2)
        {
            int pa=find(v);
            res[id]=dis[u]+dis[v]-2*dis[pa];
        }
    }
    mark[u]=2;
}
void YD()
{
    cin>>n>>q;
    int a,b,c;
    fore(i,1,n-1)
    {
        cin>>a>>b>>c;
        adj[a].push_back(b);
        cost[a].push_back(c);
        adj[b].push_back(a);
        cost[b].push_back(c);
    }
    
    fore(i,1,q)
    {
        cin>>a>>b;
        query[a].push_back({b,i});
        query[b].push_back({a,i});
    }
    fore(i,1,n) p[i]=i;
    DFS(1,0);//get dis
    Tarjan(1);
    fore(i,1,q)
    {
        cout<<res[i]<<endl;
    }
}
View Code

 

4、LCA可以解决多次查询树上两点间距离的问题

  设dis[]为到根的距离,则x、y之间的距离为 dis[x]+dis[y]-2*dis[pa],pa为LCA(x,y)

  例题:https://www.acwing.com/problem/content/1173/

  代码:

#include<bits/stdc++.h>

#define fore(x,y,z) for(LL x=(y);x<=(z);x++)
#define forn(x,y,z) for(LL x=(y);x<(z);x++)
#define rofe(x,y,z) for(LL x=(y);x>=(z);x--)
#define rofn(x,y,z) for(LL x=(y);x>(z);x--)
#define pub push_back
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second

using namespace std;
typedef long long LL;
typedef pair<int,int> PII;
typedef pair<LL,LL> PLL;
int n,q;
const int N=10010;
vector<int> adj[N];
vector<int> cost[N];
vector<pair<int,int>> query[N];//v,qid
int res[2*N];
int dis[N];
int mark[N];
int p[N];
void DFS(int u,int pa)
{
    for(int i=0;i<adj[u].size();i++)
    {
        int nxt=adj[u][i];
        int c=cost[u][i];
        if(nxt!=pa)
        {
            dis[nxt]=dis[u]+c;
            DFS(nxt,u);
        }
    }
}

int find(int x)
{
    if(p[x]!=x) p[x]=find(p[x]);
    return p[x];
}
void Tarjan(int u)
{
    mark[u]=1;
    for(int i=0;i<adj[u].size();i++)
    {
        int nxt=adj[u][i];
        int c=cost[u][i];
        if(mark[nxt]==0)//子节点而非父节点
        {
            Tarjan(nxt);
            p[nxt]=u;
        }
    }
    for(auto [v,id]:query[u])
    {
        if(mark[v]==2)
        {
            int pa=find(v);
            res[id]=dis[u]+dis[v]-2*dis[pa];
        }
    }
    mark[u]=2;
}
void YD()
{
    cin>>n>>q;
    int a,b,c;
    fore(i,1,n-1)
    {
        cin>>a>>b>>c;
        adj[a].push_back(b);
        cost[a].push_back(c);
        adj[b].push_back(a);
        cost[b].push_back(c);
    }
    
    fore(i,1,q)
    {
        cin>>a>>b;
        query[a].push_back({b,i});
        query[b].push_back({a,i});
    }
    fore(i,1,n) p[i]=i;
    DFS(1,0);//get dis
    Tarjan(1);
    fore(i,1,q)
    {
        cout<<res[i]<<endl;
    }
}
 
int main()
{
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    int T=1;
    //cin >> T;
    while (T--)
    {
        YD();
    }
    return 0;
}
View Code

 

posted @ 2022-09-16 16:37  80k  阅读(66)  评论(0编辑  收藏  举报