图论-最近公共祖先-在线树上倍增
有关概念:
最近公共祖先(LCA,Lowest Common Ancestors):对于有根树T的两个结点u、v,最近公共祖先表示u和v的深度最大的共同祖先。
树上倍增是求LCA的在线算法(对于每一个询问输入后即计算)
思路:
fa[i][j]表示编号为j的结点从下往上的第2i个祖先
即fa[0][j]表示j的父结点,递推式为fa[i][j]=fa[i-1][fa[i-1][j]],即j的第2i个祖先为j的第2i-1个祖先的第2i-1个祖先
另:不存在第2i个祖先(即2i超过j的深度)时,f[i][j]指向根结点
求LCA的过程分两步,都类似于逐步逼近,先使指针从深度较深的结点向上逼近直至与另一结点深度相同,再让两个指针同时向上逼近LCA,即求出结果
样例推导:
求17、13,16、6,1、15的LCA
指针1指向13,指针2指向17
i=0时,指针2移至fa[i][17],即12,使得深度与指针1相同
i=1时,fa[i][12]==fa[i][13],两指针同时移至5,LCA(17,13)=5
指针1指向6,指针2指向16
i=1时,指针2移至fa[i][16],即8,逼近指针1的深度
i=0时,指针2移至fa[i][8],即5,使得深度与指针1相同
i=0时,fa[i][5]==fa[i][6],两指针同时移至2,LCA(16,6)=2
指针1指向1,指针2指向15
i=2时,指针2移至fa[i][15],即1,使得深度与指针1相同
发现指针1和指针2重合,LCA(1,15)=1
不知大家发现没有,指针1开始一直指向深度较小的结点,这个在程序中有体现,主要是减小代码量
1 #include<cstdio> 2 #include<algorithm> 3 using namespace std; 4 struct edge 5 { 6 int v,next,val; 7 }e[100005]; 8 int n,m,heads[50005],fa[17][50005],dis[50005],dep[50005],cnt; 9 void add(int u,int v,int val) 10 { 11 e[++cnt].next=heads[u]; 12 heads[u]=cnt; 13 e[cnt].v=v; 14 e[cnt].val=val; 15 } 16 void dfs(int u)//预处理dep和fa[0][j] 17 { 18 for(int i=heads[u];i;i=e[i].next) 19 { 20 if(e[i].v!=fa[0][u]) 21 { 22 dep[e[i].v]=dep[u]+1; 23 fa[0][e[i].v]=u; 24 dis[e[i].v]=dis[u]+e[i].val; 25 dfs(e[i].v); 26 } 27 } 28 } 29 int LCA(int u,int v) 30 { 31 if(dep[u]>dep[v])swap(u,v); 32 for(int i=16;~i;i--) 33 if(dep[fa[i][v]]>=dep[u]) 34 v=fa[i][v]; 35 if(u==v)return u; 36 for(int i=16;~i;i--) 37 if(fa[i][u]!=fa[i][v]) 38 { 39 u=fa[i][u]; 40 v=fa[i][v]; 41 } 42 return fa[0][u]; 43 } 44 int main() 45 { 46 scanf("%d",&n); 47 for(int i=1;i<n;i++) 48 { 49 int x,y,z; 50 scanf("%d%d%d",&x,&y,&z); 51 add(x,y,z); 52 add(y,x,z); 53 } 54 dep[1]=fa[0][1]=1; 55 dfs(1); 56 for(int i=1;i<=16;i++) 57 for(int j=1;j<=n;j++) 58 fa[i][j]=fa[i-1][fa[i-1][j]]; 59 scanf("%d",&m); 60 while(m--) 61 { 62 int x,y; 63 scanf("%d%d",&x,&y); 64 printf("%d\n",dis[x]+dis[y]-2*dis[LCA(x,y)]);//两点之间路径长度 65 } 66 return 0; 67 }