Luogu p3379(LCA)
传送门
题意:
求一颗个节点的树的
题目分析:
复习+学习一下三种不同的求法(特别是根据欧拉序+表求)的方法。
下面简单总结(借鉴)一下的三种求法
代码:
- 树上倍增算法(在线),预处理时间复杂度,每次询问的时间复杂度为
 该算法的核心为构建出一个倍增数组,表示第个结点的第个祖先。同时存在一个递推式:
 因此我们可以先预处理出来所有的数组,最后对于每个询问,都根据进行跳转,就可以用的时间复杂度求出两个结点的
#include <bits/stdc++.h>
#define maxn 1000005
using namespace std;
const int LOG=20;
struct Node{
    int to,next;
}q[maxn];
int head[maxn],cnt=0;
void add_edge(int from,int to){
    q[cnt].to=to;
    q[cnt].next=head[from];
    head[from]=cnt++;
}
struct LCA_ST{
    int anc[maxn][LOG],depth[maxn];
    void dfs(int x,int fa,int dis){
        anc[x][0]=fa;depth[x]=dis;
        for(int i=head[x];i!=-1;i=q[i].next){
            int to=q[i].to;
            if(to==fa) continue;
            dfs(to,x,dis+1);
        }
    }
    void init(int root,int n){
        dfs(root,-1,1);
        for(int j=1;j<LOG;j++){
            for(int i=1;i<=n;i++){
                anc[i][j]=anc[ anc[i][j-1] ][j-1];
            }
        }
    }
    void swim(int &x,int h){
        for(int i=0;h>0;i++){
            if(h&1)
                x=anc[x][i];
            h>>=1;
        }
    }
    int query(int x,int y){
        if(depth[x]<depth[y]) swap(x,y);
        swim(x,depth[x]-depth[y]);
        if(x==y) return x;
        for(int i=LOG-1;i>=0;i--){
            if(anc[x][i]!=anc[y][i]){
                x=anc[x][i];
                y=anc[y][i];
            }
        }
        return anc[x][0];
    }
}lca;
int main()
{
    memset(head,-1,sizeof(head));
    cnt=0;
    int n,m,root;
    scanf("%d%d%d",&n,&m,&root);
    for(int i=0;i<n-1;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        add_edge(a,b);
        add_edge(b,a);
    }
    lca.init(root,n);
    while(m--){
        int a,b;
        scanf("%d%d",&a,&b);
        printf("%d\n",lca.query(a,b));
    }
    return 0;
}
dfs会爆栈?我们也可以用bfs进行预处理
#include <bits/stdc++.h>
#define maxn 1000005
using namespace std;
const int LOG=20;
struct Node{
    int to,next;
}q[maxn];
int head[maxn],cnt=0;
void add_edge(int from,int to){
    q[cnt].to=to;
    q[cnt].next=head[from];
    head[from]=cnt++;
}
struct LCA_ST{
    int anc[maxn][LOG],depth[maxn];
    void bfs(int root){
        queue<int>que;
        depth[root]=0;
        anc[root][0]=root;
        que.push(root);
        while(!que.empty()){
            int x=que.front();
            que.pop();
            for(int i=1;i<LOG;i++){
                anc[x][i]=anc[ anc[x][i-1] ][i-1];
            }
            for(int i=head[x];i!=-1;i=q[i].next){
                int to=q[i].to;
                if(to==anc[x][0]) continue;
                depth[to]=depth[x]+1;
                anc[to][0]=x;
                que.push(to);
            }
        }
    }
    void swim(int &x,int h){
        for(int i=0;h>0;i++){
            if(h&1)
                x=anc[x][i];
            h>>=1;
        }
    }
    int query(int x,int y){
        if(depth[x]<depth[y]) swap(x,y);
        swim(x,depth[x]-depth[y]);
        if(x==y) return x;
        for(int i=LOG-1;i>=0;i--){
            if(anc[x][i]!=anc[y][i]){
                x=anc[x][i];
                y=anc[y][i];
            }
        }
        return anc[x][0];
    }
}lca;
int main()
{
    memset(head,-1,sizeof(head));
    cnt=0;
    int n,m,root;
    scanf("%d%d%d",&n,&m,&root);
    for(int i=0;i<n-1;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        add_edge(a,b);
        add_edge(b,a);
    }
    lca.bfs(root);
    while(m--){
        int a,b;
        scanf("%d%d",&a,&b);
        printf("%d\n",lca.query(a,b));
    }
    return 0;
}
- 欧拉序+表(在线),预处理时间复杂度为,每次询问的时间复杂度为
 个人认为,该算法是三个求解算法中最浅显易懂的算法了。
 首先我们需要知道,欧拉序即为:对有根树进行深度优先遍历,无论是递归还是回溯,每次到达一个节点就把编号记录下来,得到一个长度为的序列,成为树 的欧拉序列 。
 之后我们再记录一下出现的每个结点第一次出现的位置,最后我们可以发现,等价于在欧拉序列中区间在到之间的深度最小的结点。
 因此,此时我们就可以将求解转化成一个问题,继而,我们就可以用表进行维护。
 继而预处理的时间复杂度为,而单次的查询可以达到
#include <bits/stdc++.h>
#define maxn 1000005
using namespace std;
const int LOG=20;
struct Node{
    int to,next;
}q[maxn<<1];
int head[maxn],cnt=0;
void add_edge(int from,int to){
    q[cnt].to=to;
    q[cnt].next=head[from];
    head[from]=cnt++;
}
struct LCA_ST{
    int ST[maxn<<1][LOG];
    int value[maxn],depth[maxn<<1],first[maxn],len;
    int cal(int x,int y){
        return depth[x]<depth[y]?x:y;
    }
    void dfs(int x,int fa,int dis){
        value[++len]=x,depth[len]=dis;
        first[x]=len;
        for(int i=head[x];i!=-1;i=q[i].next){
            int to=q[i].to;
            if(to==fa) continue;
            dfs(to,x,dis+1);
            value[++len]=x;
            depth[len]=dis;
        }
    }
    void init(int root){
        len=0;
        dfs(root,-1,1);
        for(int i=1;i<=len;i++) ST[i][0]=i;
        for(int j=1;(1<<j)<=len;j++){
            for(int i=1;i+(1<<j)-1<=len;i++){
                ST[i][j]=cal(ST[i][j-1],ST[i+(1<<(j-1))][j-1]);
            }
        }
    }
    int query(int x,int y){
        int l=first[x],r=first[y];
        if(l>r) swap(l,r);
        int k=log2(r-l+1);
        return value[cal(ST[l][k],ST[r-(1<<k)+1][k])];
    }
}lca;
int main()
{
    memset(head,-1,sizeof(head));
    cnt=0;
    int n,m,root;
    scanf("%d%d%d",&n,&m,&root);
    for(int i=0;i<n-1;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        add_edge(a,b);
        add_edge(b,a);
    }
    lca.init(root);
    while(m--){
        int a,b;
        scanf("%d%d",&a,&b);
        printf("%d\n",lca.query(a,b));
    }
    return 0;
}
- 算法有待总结……

 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号