一、向上标记法
比如求x,y的lca,我们先从x向上走到根节点,中途标记每个被走过的点,然后从y向上标记,直到遇到第一个被标记的点就是他们的lca了。(不适用于多次查询操作)
时间复杂度:O(n)
二、树上倍增法
这种方法主要是利用了倍增的思想,不断地向上试探,同时利用dp不断转移到上面,然后即可求出lca,
/*
树上倍增法求最近公共祖先
时间复杂度: O ((n+m)log(n))
1、d[x]表示x的深度,不妨设d[x]>=d[y](否则x,y交换)
2、用二进制拆分的思想,把x向上调整到和y同一深度
3、若此时x=y,则说明找到了LCA,LCA就是y
4、用二进制拆分的思想,把x,y同时向上调整,变更且保持两者深度一致且不相会
5、此时x,y必定只差一步就会相会了,此时他们的父结点f[x,0]就是LCA
f[x,k]表示x的2^k辈祖先,即从x向根结点走了2^k步到达的结点
*/
#include<bits/stdc++.h>
using namespace std;
const int size=50010;
int T,n,m,tot,t;
int f[size][20],d[size],dist[size];
int ver[size*2],nxt[size*2],edge[size*2],head[size*2];
queue<int> q;
void add(int x,int y,int z){
ver[++tot]=y,edge[tot]=z,nxt[tot]=head[x],head[x]=tot;
}
void bfs(){ //预处理
q.push(1),d[1]=1;
while(q.size()) {
int x=q.front();
q.pop();
for(int i=head[x];i;i=nxt[i]){
int y=ver[i];
if(d[y]) continue;
d[y]=d[x]+1; //y的结点深度加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]; //不断更新各祖先
}
q.push(y);
}
}
}
int lca(int x,int y){ //计算树上任意两点之间的距离
if(d[x]>d[y]) swap(x,y); //令x的深度小于y的深度,即x更靠经公共祖先
for(int i=t;i>=0;i--){
if(d[f[y][i]]>=d[x]) y=f[y][i]; //y不断向上进行调整
}
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不算进行向上调整
}
return f[x][0]; //x向上调整1步即可得到lca
}
int main(){
cin>>T;
while(T--){
cin>>n>>m;
t=(int)(log(n)/log(2))+1;
for(int i=1;i<=n;i++) head[i]=d[i]=0;
tot=0;
for(int i=1;i<n;i++){
int x,y,z;
cin>>x>>y>>z;
add(x,y,z);
add(y,x,z);
}
bfs();
//进行m次查询,查询任意两点之间的距离
for(int i=1;i<=m;i++){
int x,y;
cin>>x>>y;
cout<<dist[x]+dist[y]-2*dist[lca(x,y)];
}
}
return 0;
}三、tarjan算法
该算法就是一个对向上标记法的一个优化,采用的是离线查询的操作,先记录下每次询问的操作,然后利用tarjan算法进行一个标记,同时开并查集进行一个优化。
//时间复杂度O(n+m)
#include<bits/stdc++.h>
using namespace std;
const int size=5010;
int ver[size*2],nxt[size*2],head[size*2],edge[size*2];
int fa[size],d[size],v[size],lca[size],ans[size];
vector<int> query[size],query_id[size];
int T,n,m,tot,t;
void add(int x,int y,int z){
ver[++tot]=y,edge[tot]=z;
nxt[tot]=head[x],head[x]=tot;
}
void add_query(int x,int y,int id){ //记录查询情况
query[x].push_back(y),query_id[x].push_back(id);
query[y].push_back(x),query_id[y].push_back(id);
}
int get(int x){ //寻找父结点
if(x==fa[x]) return x;
return fa[x]=get(fa[x]);
}
void tarjan(int x){
v[x]=1; //开始递归,但是还没有回溯
for(int i=head[x];i;i=nxt[i]){
int y=ver[i];
if(v[y]) continue;
d[y]=d[x]+edge[i];
tarjan(y);
fa[y]=x; //标记父结点
}
for(int i=0;i<query[x].size();i++){ //查询关于x的所有询问
int y=query[x][i],id=query_id[x][i];
if(v[y]==2){ //这个结点是访问完毕并且回溯的结点
int lca=get(y); //直接找到父结点
ans[id]=min(ans[id],d[x]+d[y]-2*d[lca]);
}
}
v[x]=2; //标记这个结点
}
int main(){
cin>>T;
while(T--){
cin>>n>>m;
for(int i=1;i<=n;i++){ //清空
head[i]=0,fa[i]=i,v[i]=0;
query[i].clear(),query_id[i].clear();
}
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);
}
for(int i=1;i<=m;i++){ //进行m次查询
int x,y;
scanf("%d%d",&x,&y);
if(x==y) ans[i]=0;
else{
add_query(x,y,i);
ans[i]=1<<30;
}
}
tarjan(1);
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
}
return 0;
}
京公网安备 11010502036488号