最近公共祖先 LCA 倍增+Tarjan实现

最近公共祖先
对于有根树T的两个结点u、v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u、v的祖先且x的深度尽可能大

通常在OI中最近公共祖先的解决办法分为在线做法和离线做法,离线做法也就是Tarjan算法,而在线做法则是倍增做法。

=========================================
Tarjan做法:利用并查集优越的时空复杂度,我们可以实现LCA问题的O(n+Q)算法,这里Q表示询问的次数。Tarjan算法基于深度优先搜索的框架,对于新搜索到 的一个结点,首先创建由这个结点构成的集合,再对当前结点的每一个子树进行搜索,每搜索完一棵子树,则可确定子树内的LCA询问都已解决。其他的LCA询 问的结果必然在这个子树之外,这时把子树所形成的集合与当前结点的集合合并,并将当前结点设为这个集合的祖先。之后继续搜索下一棵子树,直到当前结点的所 有子树搜索完。这时把当前结点也设为已被检查过的,同时可以处理有关当前结点的LCA询问,如果有一个从当前结点到结点v的询问,且v已被检查过,则由于 进行的是深度优先搜索,当前结点与v的最近公共祖先一定还没有被检查,而这个最近公共祖先的包涵v的子树一定已经搜索过了,那么这个最近公共祖先一定是v 所在集合的祖先。

#include
#include
#include
#include

long long read() {
    long long num = 0, f = 1;
    char ch = getchar();
    while(!isdigit(ch)) {
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) num = num * 10 + ch - '0', ch = getchar();
    return num * f;
}

const int maxn = 500010;

using namespace std;
int father[MAXN];
int head[MAXN];
int qhead[MAXN];
int que[MAXN];

struct Edge {
    int next,to;
} edge[1000010];

struct qEdge {
    int next,to,ans;
    qEdge() {
        ans=0;
    }
} q[1000010];

int cnt;
void add(int x,int y) {
    cnt++;
    edge[cnt].to = y;
    edge[cnt].next = head[x];
    head[x] = cnt;
}

void qadd(int x,int y,int k) {
    q[k].to = y;
    q[k].next = qhead[x];
    qhead[x] = k;
}

int find(int x) {
    if(father[x] != x)    father[x] = find(father[x]);
    return father[x];
}

void unionn(int x,int y) {
    x = find(x);
    y = find(y);
    father[y] = x;
}

void tarjan(int x) {
    que[x] = 1;
    for(int i = head[x]; i; i = edge[i].next) {
        int to = edge[i].to;
        if(!que[to]) {
            tarjan(to);
            unionn(x, to);
        }
    }
    for(int i = qhead[x]; i; i = q[i].next) {
        int to = q[i].to;
        if(que[to] == 2) {
            q[i].ans = find(to);
            if(i%2) q[i+1].ans = q[i].ans;
            else q[i-1].ans = q[i].ans;
        }
    }
    que[x] = 2;
}

int main() {
    int n = read(), m = read(), s = read();
    for(int i = 1; i < n; ++i) {
        int x = read(), y = read();
        add(x, y);
        add(y, x);
        father[i] = i;
    }
    father[n] = n;
    for(int i = 1; i <= m; ++i) {
        int x = read(), y = read();
        qadd(x, y, i * 2 - 1);
        qadd(y, x, i * 2);
    }
    tarjan(s);
    for(int i =  1; i <= n; ++i) printf("%d\n",q[i *  2].ans);

    return 0;
}

========================================

倍增做法:每次询问O(logN)
deep[i] 表示 i节点的深度, P[i,j] 表示 i 的 2^j 倍祖先
那么就有一个递推式子 P[i,j]=P[P[i,j-1],j-1]
这样子一个O(NlogN)的预处理求出每个节点的 2^k 的祖先
然后对于每一个询问的点对(a, b)的最近公共祖先就是:
先判断是否 deep[a] > deep[b] ,如果是的话就交换一下(保证 a 的深度小于 b 方便下面的操作),然后把b 调到与a 同深度, 同深度以后再把a, b 同时往上调(dec(j)) 调到有一个最小的j 满足P[a,j] != P[b,,j] (a b 是在不断更新的), 最后再把 a, b 往上调 (a = P[a,0], b = P[b,0]) 一个一个向上调直到a = b, 这时 a or b 就是他们的最近公共祖先。

#include
#include
#include
#include
using namespace std;
const int MAXN=1000001;
int n,m,root;
struct node
{
    int u;
    int v;
    int next;
}edge[MAXN];
int num=1;
int head[MAXN];
int deep[MAXN];
int f[MAXN][20];
void edge_add(int x,int y)
{
    edge[num].u=x;
    edge[num].v=y;
    edge[num].next=head[x];
    head[x]=num++;
}
void build_tree(int p)
{
    for(int i=head[p];i!=-1;i=edge[i].next)
    {
        int will=edge[i].v;
        if(deep[will]==0)
        {
            deep[will]=deep[p]+1;
            f[will][0]=p;
            build_tree(will); 
        }
    }
}
void initialize_step()
{
    for(int i=1;i<=19;i++)
        for(int j=1;j<=n;j++)
            f[j][i]=f[f[j][i-1]][i-1];
}
int LCA(int x,int y)
{
    if(deep[x]for(int i=19;i>=0;i--)
        if(deep[f[x][i]]>=deep[y]) x=f[x][i];
    if(x==y)return y;
    for(int i=19;i>=0;i--)
        if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return f[x][0];
}

void read(int & x)
{
    char c=getchar();x=0;
    while(c<'0'||c>'9')c=getchar();
    while(c>='0'&&c<='9')x=x*10+c-48,c=getchar();
}

int main(){
    read(n);read(m);read(root);
    for(int i=1;i<=n;i++)head[i]=-1;
    for(int i=1;i<=n-1;i++){
        int x,y;
        read(x);read(y);
        edge_add(x,y);
        edge_add(y,x);
    }
    deep[root]=1;
    build_tree(root);
    initialize_step();
    for(int i=1;i<=m;i++){
        int x,y;
        read(x);read(y);
        printf("%d\n",LCA(x,y));
    }
    return 0;
}

你可能感兴趣的:(须长记忆)