LCA树上两点最近公共祖先(倍增算法)
对于一个树来说,有n个点,n-1个边,一定存在一个公共祖先
LCA就是寻找两点最近的公共祖先,并记录下路径
倍增是一种以数组anc[j][i]存储j这个结点的2^i的祖先,利用二分对两个节点同时往上走
预处理时间复杂度O(nlogn),每次查询时间复杂度O(logn),总时间复杂度O(nlogn+qlogn)。
Cerror是杭州市的市长。如您所知,这个城市的交通系统非常糟糕,到处都有交通拥堵。现在,Cerror发现它们的主要原因是道路分布设计不佳,他想改变这种状况。
为了实现这个项目,他将城市划分为N个区域,可以将其视为单独的点。他认为最好的设计是连接所有地区最短路的设计,他要求你检查他的一些设计。
现在,他给出了一个代表他的道路设计的非循环图,你需要找到连接一组三个区域的最短路径。
输入
输入包含多个测试用例!在每种情况下,第一行包括整数N(1 <N <50000),表示从0到N-1索引的区域的数量。在以下N-1行中的每一行中,存在三个整数Ai,Bi,Li(1 <Li <100),表示在区域Ai和区域Bi之间存在长度为Li的道路。然后是一个整数Q(1 <Q <70000),你需要检查的区域组数。然后在以下每个Q行中,有三个整数Xi,Yi,Zi,表示要检查的三个区域的索引。
处理到文件结尾。
产量
每个测试用例的Q行。在每行输出一个整数,指示连接三个区域的最小路径长度。
在每个测试用例之间输出一个空行。
样本输入
4 0 1 1 0 2 1 0 3 1 2 1 2 3 0 1 2 5 0 1 1 0 2 1 1 3 1 1 4 1 2 0 1 2 1 0 3
样本输出
3 2 2 2
思路:
1:建树,以邻接表链式前向星的方法存储点与点的关系
2:dis[i]数组存储的是i点到树的根节点的距离(比如求两点的距离,只要求出这两个点到跟接待您的距离加起来再减去两倍的最近公共祖先的结点到跟接待您的距离)
deep[]数组存储的是每个结点的深度(根节点在最上面,深度初始化为0)
anc[u][i]数组存储的是u的2^i的祖先是谁
3:从根节点开始进行dfs
每调用一次dfs()都需要对参数结点每一个的2^i祖先进行赋值,然后遍历参数节点相连的每一个点,更新其到根节点的距离和deep数组,但是如果这个点已经走过,就不需要更新了,所以就有 if(edge[i].to==dad) continue;
anc[t][0]=u;意思是把u作为t的父节点(2^0=1) 然后递归调用dfs(t,u);
4:lca()
首先要使u点比v点更深,否则就swap(u,v)
然后使u,v调整到同一深度,如果u==v说明u和v在同一边,最近的公共祖先就直接找到了
然后以倍增思想一起往上跳(如果u点的2^i的祖先节点和v的2^i的祖先节点不同才一起跳,为了防止跳到一个相同祖先后不是最近的那一个)
完整代码
#include<bits/stdc++.h>
const int INF=0x3f3f3f3f;
const int xmax=100007;
using namespace std;
#define ll long long
struct node
{
int to;
int len;
int next;
}edge[xmax];
int head[xmax];
int cnt;
void init()
{
memset(head,-1,sizeof(head));
cnt=0;
}
void addedge(int u,int v,int w)
{
edge[cnt].to=v;
edge[cnt].len=w;
edge[cnt].next=head[u];
head[u]=cnt++;
}
int anc[xmax][21];
int dis[xmax];
int deep[xmax];
void dfs(int u,int dad)
{
for(int i=1;i<=20;i++)
{
anc[u][i]=anc[anc[u][i-1]][i-1];
}
for(int i=head[u];~i;i=edge[i].next)
{
int t=edge[i].to;
if(t==dad) continue;
dis[t]=dis[u]+edge[i].len;
deep[t]=deep[u]+1;
anc[t][0]=u;
dfs(t,u);
}
}
int lca(int u,int v)
{
if(deep[u]<deep[v]) swap(u,v); //使u比v更深
for(int i=20;i>=0;i--) //调整到同一深度
{
if(deep[anc[u][i]]>=deep[v])
u=anc[u][i];
}
if(u==v) return u; //如果u,v在同一边,直接返回u或v即可
for(int i=20;i>=0;i--)
{
if(anc[u][i]!=anc[v][i]) //不相同才跳
{
u=anc[u][i];
v=anc[v][i];
}
}
return anc[u][0];
}
int main()
{
int flag=0;
int n;
while(scanf("%d",&n)!=EOF)
{
if(flag==1)
printf("\n");
flag=1;
init();
int u,v,w;
for(int i=0;i<n-1;i++)
{
scanf("%d%d%d",&u,&v,&w);
addedge(u,v,w);
addedge(v,u,w);
}
dis[0]=0;
deep[0]=0;
anc[0][0]=0;
dfs(0,0);
int m;
scanf("%d",&m);
//求三个点之间的最短据距离,就求两两之间的最短距离加起来除以2就行
for(int i=0;i<m;i++)
{
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
int k1=lca(x,y);
ll ans=0;
ans+=(dis[x]+dis[y]-2*dis[k1]);
int k2=lca(x,z);
ans+=(dis[x]+dis[z]-2*dis[k2]);
int k3=lca(y,z);
ans+=(dis[y]+dis[z]-2*dis[k3]);
printf("%lld\n",ans/2);
}
}
return 0;
}