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

来源:互联网 发布:软件需求变更流程图 编辑:程序博客网 时间:2024/05/17 08:48

最近公共祖先
对于有根树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<iostream>#include<cstdlib>#include<cstdio>#include<cctype>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<iostream>#include<cstdio>#include<cstring>#include<cmath>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]<deep[y])swap(x,y);    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;}
原创粉丝点击