最近公共祖先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 }