LCA是非常玄学的一个东西。它的主要思想就是倍增。
我们可以用二进制来维护这个东西,当然关于这个的好多东西比如维护和,最小值时可以用到。
假设,我们要查询的两个节点是节点u和节点v,其中u的深度为p,v的深度为q。首先,我们将二者的中较深的进行调整,向上跳,跳到同一层。然后二者同时向上跳,直到跳到同一层为止。但是这个跳的方法和以往的不一样,它是以按2的次幂的形式跳,也就是跳2^0,2^1,2^2等等层。因为对于任意一个整数n,它都可以找到唯一的一组x1,x2,x3,.....
来满足一下等式:
n=2^x1+2^x2+2^x3+......
所以对于任意一个整数,都能通过按2的次幂跳来实现,这个时候规定从高次幂到低次幂的跳。举个例子,对于6,我们知道它等于6=2^1+2^2,当跳的时候,是先跳2^2,再跳2^1。另一方面,我们知道每一个节点的层数,所以我们就能通过logn来求出那个2的次幂的上限。但我们可能会考虑到一个问题,那就是如果当节点u和节点v已经再同一层的时候,可能它们先跳一次高次幂的层数时就已经找到了公共祖先,但这个却不是最近的公共祖先。对于这个问题我们这样解决,我们不去找公共祖先,而是去找最近公共祖先下一层的那个节点,也就是最近公共祖先的子节点,这样就能避免一次跳过头,通过判断条件就能实现。
为了实现在跳了2的多少次幂后能够很快的定位到节点,我们为每个节点维持一个数组anc[i][j],表示节点i跳了2^j次幂层之后到达的节点。如下图,anc[7][1]就是节点1对于anc[i][j]数组的初始化问题:
1.当j=0时,显然anc[i][j]就是节点i的父节点,就像普通解法的father数组意义一样
2.当j>0时,我们不能直接求出跳多层的值,然后我们可以转化,因为anc[i][j]=i+2^j=i+2^(j-1)+2^(j-1)=
(i+2^(j-1))+2^(j-1)=anc[anc[i][j-1]][j-1],所以就能这样地退出来。也就是anc[i][j]=anc[anc[i][j-1]][j-1]。
代码实现:
#include<stdio.h> #include<string.h> #include<vector> #include<stdlib.h> #include<math.h> #define max 40001 #define maxl 25 using namespace std; typedef struct { int from,to,w; }edge;//这个结构体用来存储边 vector<edge>edges; vector<int> G[max]; //保存边的数组 int s[max][maxl],gw[max][maxl];//x向上跳2^i次方的节点,x到他上面祖先2^i次方的距离 int depth[max];//深度 int n,m,N; void addedge(int x,int y,int w)//把边保存起来的函数 { edge a={x,y,w},b={y,x,w}; edges.push_back(a); edges.push_back(b); G[x].push_back(edges.size()-2); G[y].push_back(edges.size()-1); } void dfs(int x)//dfs建图 { for(int i=1;i<=N;i++)//第一个几点就全部都是0咯,第二个节点就有变化了,不理解的话建议复制代码输出下这些数组 { s[x][i]=s[s[x][i-1]][i-1]; gw[x][i]=gw[x][i-1]+gw[s[x][i-1]][i-1]; // if(s[x][i]==0) break; } for(int i=0;i<G[x].size();i++) { edge e = edges[G[x][i]]; if(e.to!=s[x][0])//这里我们保存的是双向边所以与他相连的边不是他父亲就是他儿子父亲的话就不能执行,不然就死循环了。 { depth[e.to]=depth[x]+1;//他儿子的深度等于他爸爸的加1 s[e.to][0]=x;//与x相连那个节点的父亲等于x gw[e.to][0]=e.w;//与x相连那个节点的距离等于这条边的距离 dfs(e.to);//深搜往下面建 } } } void Init(){ //n为节点个数 N = floor(log(n + 0.0) / log(2.0));//最多能跳的2^i祖先 depth[1]=0;//根结点的祖先不存在,用-1表示 memset(s,0,sizeof(s)); memset(gw,0,sizeof(gw)); dfs(1);//以1为根节点建树 } int lca(int a,int b) { if(depth[a] > depth[b]) swap(a, b);//保证a在b上面,便于计算 int ans = 0; for(int i = N; i >= 0; i--) //类似于二进制拆分,从大到小尝试 if(depth[a] < depth[b] && depth[s[b][i]] >= depth[a])//a在b下面且b向上跳后不会到a上面 ans +=gw[b][i], b=s[b][i];//先把深度较大的b往上跳 for(int j=N;j>=0;j--)//在同一高度了,他们一起向上跳,跳他们不相同节点,当全都跳完之后s【a】【0】就是lca,上面有解释哈。 { if(s[a][j]!=s[b][j]) { ans+=gw[a][j]; ans+=gw[b][j]; a=s[a][j]; b=s[b][j]; } } if(a!=b)//a等于b的情况就是上面土色字体的那种情况 { ans+=gw[a][0],ans+=gw[b][0]; } return ans; } int main() { int t ; scanf("%d",&t); while(t--) { scanf("%d%d",&n,&m); for(int i=1;i<n;i++) { int x,y,w; scanf("%d%d%d",&x,&y,&w); addedge(x,y,w); } Init(); for(int i=1;i<=m;i++) { int x,y; scanf("%d%d",&x,&y); printf("%d\n",lca(x,y)); } } }