LCA两种解法(RMQ+欧拉序、树上倍增)模板

LCA(最近公共祖先)解法模板,附有详细代码注释

我们知道,最近公共祖先是指有根树上找出任意两个节点,u,v的最近的公共祖先。
这是洛谷的模板题:
最近公共祖先LCA
解释都在代码里:

树上倍增

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0)
#define ull unsigned ll
#define uint unsigned
#define pai pair<int,int>
#define pal pair<ll,ll>
#define IT iterator
#define pb push_back
#define fi first
#define se second
#define For(i,j,k) for (int i=(int)(j);i<=(int)(k);++i)
#define Rep(i,j,k) for (int i=(int)(j);i>=(int)(k);--i)
#define endl '\n'
#define ll long long
const int N=500010;
int head[N],tot;
struct node
{
    int to,nxt;
}tree[N<<1];//定义节点
void add(int x,int y)
{
    tree[++tot].to=y;
    tree[tot].nxt=head[x];
    head[x]=tot;
}//链式前向星存图
int depth[N];//depth数组用来存每一个节点的深度;
int fa[N][20];//fa[i][j]表示节点i的2^j级祖先;
int lg[N];//倍增遍历的lg数组;

void dfs(int now,int fath)//now表示当前节点,fath表示它的直接父亲节点
{
    fa[now][0]=fath;//当前节点的父亲;
    depth[now]=depth[fath]+1;//当前节点的深度就是父亲节点的深度+1;
    for(int i=1;i<lg[depth[now]];++i)
    {
        fa[now][i]=fa[fa[now][i-1]][i-1];//实际上是一个小递归,就是一个节点的2^i级祖先为它的2^i级祖先的2^i级祖先;
    }
    for(int i=head[now];i;i=tree[i].nxt)
    {
        if(tree[i].to!=fath)
            dfs(tree[i].to,now);
    }
}//以上dfs实际上是一种预处理,找出每一个节点的2^i级祖先。
int LCA(int x,int y)
{
    if(depth[x]<depth[y])//为了方便处理,我们设x的深度大于y的深度;
        swap(x,y);
    while(depth[x]>depth[y])
    {
        x=fa[x][lg[depth[x]-depth[y]]-1];//先让x跳到与y的同一深度;
    }
    if(x==y)//如果x是y的祖先,那么它们的公共祖先为x;
        return x;
    for(int k=lg[depth[x]]-1;k>=0;--k)//两个节点一起往上跳,每次的幅度就是lg数组
        if(fa[x][k]!=fa[y][k])//因为一直不相等,所以我们会跳到LCA的下一层
        x=fa[x][k],y=fa[y][k];
    return fa[x][0];//最后返回下一层的父亲,也就是我们要求的LCA;
}
int main()
{
    int n,m,s;
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<=n-1;++i)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    for(int i=1;i<=n;++i)
    {
        lg[i]=lg[i-1]+(1<<lg[i-1]==i);
    }//常数优化;
    dfs(s,0);
    for(int i=1;i<=m;++i)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        printf("%d\n",LCA(x,y));
    }
    return 0;
}

RMQ+欧拉序:
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0)
#define ull unsigned ll
#define uint unsigned
#define pai pair<int,int>
#define pal pair<ll,ll>
#define IT iterator
#define pb push_back
#define fi first
#define se second
#define For(i,j,k) for (int i=(int)(j);i<=(int)(k);++i)
#define Rep(i,j,k) for (int i=(int)(j);i>=(int)(k);--i)
#define endl '\n'
#define ll long long
const int maxn = 500005;
const int maxm = 1000005;
int head[maxn],nxt[maxm],to[maxm],cnt;
int id[maxm],vis[maxm],depth[maxm],tot;
int f[maxm][20],lg[maxm];
//定义dp[i][j] 表示下标i开始长度为2^j的区间的极值。如:dp[2][1]表示区间[2,3]的极值
inline int read()
{
    int sum = 0,p = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9')
    {
        if(ch == '-')
            p = -1;
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        (sum *= 10)+= ch - '0';
        ch = getchar();
    }
    return sum * p;
}//快读

void add(int x,int y)//链式前向星加边
{
    nxt[++cnt] = head[x];
    to[cnt] = y;
    head[x] = cnt;
    return;
}

void dfs(int u,int fa,int dep)//u代表当前节点,fa是当前节点的父亲,dep是当前节点的深度
{
    id[u] = ++tot;//id[u]表示在欧拉序中第一次被访问时的下标。
    vis[tot] = u;//vis[i]存的是第i个节点在欧拉序中的编号
    depth[tot] = dep;//depth[]是存节点的深度
    for(int i = head[u];i;i = nxt[i])//链式前向星遍历
    {
        int v = to[i];
        if(v == fa)
            continue;
        dfs(v,u,dep+1);
        vis[++tot] = u;
        depth[tot] = dep;
    }
    return;
}//欧拉序处理每个节点得到id[u],vis[i],depth[i];

void RMQ()
{
    for(int i = 1;i <= tot;i++)//tot是跑过欧拉序后的总点数
        lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);//常数优化预处理
    for(int i = 1;i <= tot;i++)
        f[i][0] = i;
    for(int j = 1;(1 << j) <= tot;j++)
        for(int i = 1;i + (1 << j) - 1 <= tot;i++)
        {
            int a = f[i][j-1];
            int b = f[i + (1 << (j - 1))][j - 1];
            if(depth[a] <= depth[b])
                f[i][j] = a;
            else
                f[i][j] = b;
        }
    return;
}

int LCA(int x,int y)
{
    int r = id[x];
    int l = id[y];
    if(r < l)
        swap(r,l);
    int k = lg[r - l + 1] - 1;
    int a = f[l][k];
    int b = f[r - (1 << k) + 1][k];
    if(depth[a] <= depth[b])
        return vis[a];
    else
        return vis[b];
}

int main()
{
    int n = read(),m = read(),s = read();
    int x,y;
    for(int i = 1;i < n;i++)
    {
        x = read(),y = read();
        add(x,y);
        add(y,x);
    }
    dfs(s,0,1);
    RMQ();
    for(int i = 1;i <= m;i++)
    {
        x = read(),y = read();
        printf("%d\n",LCA(x,y));
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_45963335/article/details/107912901