树上倍增求LCA的步骤:
1.预处理:节点的深度d、到根节点的距离dist、该点向上走2^k步能够到达的点:f数组。
2.lca:
①.将两个节点调整到同一个深度,只调整深的那个即可。
②.如果①结束后,这两个点重合,说明该点就是所求的点。
③.否则,从大往小开始试跳跃的步数,直至将这两个点调整为目标点的两个子节点。
最后两点的父节点就是所求的LCA。
例题:HDU 2568:
http://acm.hdu.edu.cn/showproblem.php?pid=2586
题意:给一颗n个点的树以及n-1条边的权值,m次查询,查询指定两个点之间的距离。
分析:
树之间的路径肯定就是两点之间的最短路了,但是如果弗洛伊德算法40000*40000*10会超时,
dijkstra算法的话15*200*40000*10也会超时。所以那几个求最短路的算法都不能用了。
然后根据题解,用 LCA的方法时间复杂度 o((N+M)logN)。
因为x到y的距离,可以看做x到根节点的距离+y到根节点的距离-2*LCA到根节点的距离。
所以可以先求LCA,又因为树作为特殊的图,两节点之间有且仅有一条通路且一定为最短路,
所以求深度、求最短路的操作是可以同时进行的(spfa,dijkstra均可以),最后,套用上面
的公式即出结果。
AC代码:
#define ll long long
using namespace std;
const int SIZE=50010;
int f[SIZE][20],d[SIZE],dist[SIZE];
int ver[2*SIZE],Next[2*SIZE],edge[2*SIZE],head[SIZE];
int T,n,m,tot,t;
queue <int> q;
void add(int x,int y,int z)
{
ver[++tot]=y;
edge[tot]=z;
Next[tot]=head[x];
head[x]=tot;
}
void bfs()//预处理,求深度、最短路,f数组;就是SPFA,时间复杂度O(NlogN)
{
q.push(1);
d[1]=1;
while(q.size())
{
int x=q.front();
q.pop();
for(int i=head[x];i;i=Next[i])
{
int y=ver[i];
if(d[y])continue;
d[y]=d[x]+1; //求节点的深度;
dist[y]=dist[x]+edge[i];//距离根节点的距离;
f[y][0]=x;
for(int j=1;j<=t;++j)
{
f[y][j]=f[f[y][j-1]][j-1];
//动态规划,y点 跳(2^j)步 能到达哪个点。
//cout<<y<<" "<<j<<" "<<f[y][j]<<endl;
q.push((y));
}
}
}
}
int lca(int x,int y)//求LCA,时间复杂度:o(Log(N))
{
if(d[x]>d[y])swap(x,y);
for(int i=t;i>=0;--i)//倒着走******
if(d[f[y][i]]>=d[x])y=f[y][i];
if(x==y)return x;
for(int i=t;i>=0;--i)
if(f[x][i]!=f[y][i])x=f[x][i],y=f[y][i];
//注意一下:x是不断变化的,而i整个一套下来;
return f[x][0];//一直更新,直至x、y为目标点下面的那两个点;
}
int main()
{
cin>>T;
while(T--)
{
cin>>n>>m;
t=(int)(log(n))/log(2)+2;
//这里限制向上的最大步数,为什么是这个?不知道.
cout<<t<<endl;
for(int i=0;i<=n;++i)head[i]=d[i]=0;
tot=0;
for(int i=1;i<n;++i)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
bfs();
//就是一个求最短路的spfa,同时求深度和f数组的值
while(m--)
{
int x,y;
cin>>x>>y;
cout<<dist[x]+dist[y]-2*dist[lca(x,y)]<<endl;
}
}
return 0;
}
The end;