[LGOJ]P3379【模板】最近公共祖先(LCA)[倍增, 树剖]

lca最暴力朴素的求法就是x,y一次次往上跳, 这样效率显然很低
考虑优化, 有两种途经:

  • dfs预处理一个倍增找父亲节点的数组, x,y成倍的往上跳, 时间复杂度降为log级
  • 利用树链剖分把树处理成一些链, 每次调到当前链的链顶, 直至x,y在同一条链上, 复杂度log级(一共不超过logn条重链)
    详见代码
    倍增
#include<cstdio>
#define re register
#define in inline
in int read()
{
    int s=0,b=1;
    char ch;
    do{
        ch=getchar();
        if(ch=='-') b=-1;
    }while(ch<'0' || ch>'9');
    while(ch>='0' && ch<='9')
    {
        s=s*10+ch-'0';
        ch=getchar();
    }
    return b*s;
}
int n,m,s;
struct edge{
    int t,next;
}e[500001*2];
int cnt=0,head[500001];
in void add(int f,int t)
{
    ++cnt;
    e[cnt].t=t;
    e[cnt].next=head[f];
    head[f]=cnt;
}
int f[500001][100],d[500001]; //f[i][j]: i号节点往上走2^j次的父亲编号. d[i]: i号节点的深度(root深度为1)
int log[500001];
void dfs(int now,int fa)
{
    d[now]=d[fa]+1;
    f[now][0]=fa;
    for(re int i=1;(1<<i)<=d[now];++i)//注意不能跳到root上面去了
        f[now][i]=f[f[now][i-1]][i-1]; //预处理出所有倍增跳法的结果, 利用now的2^i辈父亲 = 2^(i-1)辈父亲的2^(i-1)辈父亲, 而now的2^(i-1)辈父亲之前已经算出来. 类似于递推.
    for(re int i=head[now];i;i=e[i].next)
        if(e[i].t!=fa) dfs(e[i].t,now);
}//Dfs预处理出f数组, 便于一会儿倍增地向上跳跃. 顺便求d数组.
in int lca(int x,int y)
{
    if(d[x]<d[y])
    {
        int t=x;
        x=y;
        y=t;
    }//保证x在y下
    while(d[x]!=d[y]) x=f[x][log[d[x]-d[y]]]; //x先跳到与y同深度. 这时可以根据f数组倍增地向上跳(利用了每个数都可以写成二进制), 比如本来要跳13(二进制: 1101)下, 现在可以先跳2^3下, 再跳2^2下, 再跳2^0下.
    if(x==y) return x;
    for(re int i=log[d[x]];i>=0;--i)
        if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; //同理, x y同步倍增地向上跳, 直至它们有相同的父亲. 不能跳到x==y, 因为公共祖先为root时这样判断会有玄学错误.
    return f[x][0]; //x y的父亲即公共祖先.
}
int main()
{
    n=read();
    m=read();
    s=read();
    int x,y;
    int t=1;
    log[1]=0;
    for(re int i=2;i<=n;++i)
    {
        if((1<<(t+1))==i) t++;
        log[i]=t;
    }//预处理出log_2(1~n)的值;
    for(re int i=1;i<=n-1;++i)
    {
        x=read();
        y=read();
        add(x,y);
        add(y,x);
    }
    dfs(s,0);
    for(re int i=1;i<=m;++i)
    {
        x=read();
        y=read();
        printf("%d\n",lca(x,y));
    }
    return 0;
}

树剖

//sorry, 会树剖就是可以为所欲为.jpg
#include<cstdio>
#define in inline
#define re register
#define ll long long
in int read()
{
    int s=0,b=0;char ch;
    do{ch=getchar();if(ch=='-') b=1;}while(ch<'0'||ch>'9');
    while(ch>='0'&&ch<='9'){s=(s<<3)+(s<<1)+(ch^48);ch=getchar();}
    return b?-s:s;
}
const int N=500001;
int n,m,root;
struct edge{
    int t,nxt;
}e[N*2];
int cnt,head[N];
int id[N],f[N],d[N],subt[N],son[N],top[N];
in void add(int f,int t)
{
    ++cnt;
    e[cnt].t=t,e[cnt].nxt=head[f],head[f]=cnt;
    ++cnt;
    e[cnt].t=f,e[cnt].nxt=head[t],head[t]=cnt;
}
void dfs1(int now,int fa)
{
    d[now]=d[fa]+1,f[now]=fa,subt[now]=1;
    int maxson=0;
    for(re int i=head[now];i;i=e[i].nxt)
    {
        if(e[i].t==fa) continue;
        dfs1(e[i].t,now);
        subt[now]+=subt[e[i].t];
        if(subt[e[i].t]>maxson) maxson=subt[e[i].t],son[now]=e[i].t;
    }
}
void dfs2(int now,int topfa)
{
    ++cnt;
    id[now]=cnt,top[now]=topfa;
    if(!son[now]) return;
    dfs2(son[now],topfa);
    for(re int i=head[now];i;i=e[i].nxt)
        if(e[i].t!=f[now] && e[i].t!=son[now]) dfs2(e[i].t,e[i].t);
}
in int lca(int x,int y)
{
    while(top[x]!=top[y])
    {
        if(d[top[x]]<d[top[y]]) x^=y,y^=x,x^=y;
        x=f[top[x]];
    }
    if(d[x]<d[y]) x^=y,y^=x,x^=y;
    return y;
}
signed main()
{
    n=read(),m=read(),root=read();
    for(re int i=1;i<=n-1;++i){int u=read(),v=read();add(u,v);}
    dfs1(root,0);
    cnt=0;
    dfs2(root,root);
    for(re int i=1;i<=m;++i)
    {
        int x=read(),y=read();
        printf("%d\n",lca(x,y));
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/cgazn/p/10388125.html