最近公共祖先LCA

对于一棵树来说,树上两个结点有LCA。LCA是指两个结点所有公共祖先中,深度最大的。LCA有很多性质,比如树上两点间最短路径会经过LCA(无负边权),树的DFS序中LCA是两点间深度最小的等等。

LCA据说是NOIP几乎每年必考内容,如何高效率地求LCA很关键。不难想到一种暴力做法,从两个点开始往上蹦,第一次重合处就是LCA。时间复杂度是O(n)的,难以满足要求。常用的正确做法是倍增。我们可以预处理出每个结点的2^0倍祖先(即父亲),2^1倍祖先(父亲的父亲),2^2倍祖先(父亲的父亲的父亲的父亲),...然后在蹦的时候就可以一次蹦很高了。相当于是将两个结点间深度差看成二进制数,去枚举二进制位。

具体来讲,我们先预处理出每个结点的深度及各种父亲。深度的话,子结点深度=父结点深度+1;设fa[i][j]表示结点i的2^j倍父亲,则fa[i][j]=fa[fa[i][j-1]][j-1],结点i的2^j倍父亲=结点i的2^(j-1)倍父亲的2^(j-1)倍父亲,没毛病。然后我们让深度较大的结点往上蹦,蹦到与另一结点在同一层上,若此时两结点重合,则说明深度较小结点是另一结点的祖先;否则我们让两个结点一起蹦,蹦到重合。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 int n,fa[maxn][20],int d[maxn],head[maxn],eid;
 6 struct edge { //应保证由父亲可以到孩子,由孩子可以到父亲
 7     int v,next; //此处两个条件分开实现
 8 } E[maxn];
 9 void insert(int u,int v) {
10     E[eid].v=v;
11     E[eid].next=head[u];
12     head[u]=eid++;
13 }
14 void dfs(int u) {
15     for(int p=head[u];p+1;p=E[p].next) {
16         int v=E[p].v;
17         d[v]=d[u]+1; //更新深度
18         for(int i=1;(1<<i)<=d[v];++i) //更新结点的各种父亲,也可以用for循环,先枚举是几倍,再枚举各个结点得到
19             fa[v][i]=fa[fa[v][i-1]][i-1]; //for(int j=0;(1<<j)<=n;++j)
20         dfs(v);                           //    for(int i=1;i<=n;++i) fa[i][j]=fa[fa[i][j-1]][j-1];
21     }
22 }
23 int lca(int x,int y) {
24     int i,j;
25     if(d[x]<d[y]) swap(x,y); //保证先处理深度较大的
26     for(i=0;(1<<i)<=d[x];++i);--i; //得到最大深度(或接近)
27     for(j=i;j>=0;--j) //深度较大结点向上蹦
28         if(d[x]-(1<<j)>=d[y]) x=fa[x][j];
29     if(x==y) return x; //特判重合
30     for(j=i;j>=0;--j) //一起蹦,若蹦完不重合(一定未到达祖先,更不用说LCA),则蹦
31         if(fa[x][j]!=fa[y][j]) x=fa[x][j],y=fa[y][j];
32     return fa[x][0]; //再蹦就重合了,那再蹦一次就是LCA
33 }

 

posted @ 2018-09-15 08:54  Mr^Kevin  阅读(220)  评论(0编辑  收藏  举报