题意很简单,给一棵树,给你已知的每条边的权值,输入一个起点 s 一个重点 t 求二者间距离。
思路很简单,要想从 a 跑到 b 只需要找到二者的 最近公共祖先 lca,之后把 根到 i 节点 距离设为 dis[i] ,
最终结果 就是 dis[s] +dis[t] - dis[lca] * 2
那么问题来怎么求 lca 呢?
首先 我们想一下最暴力的方法
对于这样一张图 我们该如何去找 lca 呢?
找到 s 找到 t 然后两个人一点点找父节点,当然 对图分一下层,避免 s 或者 t 在找 lca 时 找到了 lca 上一层的节点,这样肯定是出问题的,所以 我们开一个 deep 数组来记录深度,避免找过头。
之后 暴力向上找就可以了吧。。。。
但是 这样做。。如果情况够差,很明显耗时 太长了。。。不合适。。
那么我们有没有一些优化的方法?
那就是 倍增法
开一个数组 p[x][i] ,这个数组表示标号为x节点向上跳2^i步的节点。例如grand[5][0]=2,因为5这个节点向上跳2^0次方个就是2,当然所有节点向上跳一个就是他的父亲节点,那grand[x][1]=什么呢?等于p[ p[x][0] ][ 0 ];
那么这样的话 我们再从高到低依次跳上去,不就节省了很多时间吗。
套用网络大犇博客上写的一些规律(ground = p)
既grand[x][i]=grand[grand[x][i-1]][i-1],为什么呢,2^4=2^3+2^3,表示 x向上跳2的i次的节点=x向上跳i-1次方的这个节点向上跳i-1次方,有点绕口。例如 grand[13][2]=grand[6][2]=g[g[rand][i-1]][i-1];
当然 现在这么看还有个问题,如果 s t 不在一样的深度,这样跳会不会出现问题。
当然 肯定第一步 先得把两个节点深度调整成一致的,调整跟深度低的一样。
然后就两个节点一起向上跳一个2^j。
当然 我们来看代码:
首先是预处理,dfs 一下,确定深度和父节点还有权值
void dfs(int u,int pre,int t)
{
int i;
deep[u]=t;//深度
f[u]=pre;//父节点
for(i=0;i<e[u].size();i++)
{
int v=e[u][i].to;
if(v!=pre)
{
dis[v]=dis[u]+e[u][i].w;//距离根的距离
dfs(v,u,t+1);
}
}
}
之后 我们输入不同边的权值,最后根据已知建成的图 来更新一下父节点数组
void init()
{
//p[i][j]表示i结点的第2^j祖先
int i,j;
for(j=0;(1<<j)<=n;j++)
for(i=1;i<=n;i++)
p[i][j]=-1;
for(i=1;i<=n;i++)
p[i][0]=f[i];
for(j=1;(1<<j)<=n;j++)
for(i=1;i<=n;i++)
if(p[i][j-1]!=-1)
p[i][j]=p[p[i][j-1]][j-1];//i的第2^j祖先就是i的第2^(j-1)祖先的第2^(j-1)祖先
}
最后 输入 s t 找到 lca
int lca(int a,int b)//最近公共祖先
{
int i,j;
if(deep[a]<deep[b])
swap(a,b);
for(i=0;(1<<i)<=deep[a];i++);
i--;
//使a,b两点的深度相同
for(j=i;j>=0;j--)
if(deep[a]-(1<<j)>=deep[b])
a=p[a][j];
if(a==b)
return a;
//倍增法,每次向上进深度2^j,找到最近公共祖先的子结点
for(j=i;j>=0;j--)
{
if(p[a][j]!=-1&&p[a][j]!=p[b][j])
{
a=p[a][j];
b=p[b][j];
}
}
return f[a];
}
感谢大牛们的思路和板子。。。。
之后 我们就可以开心的 A 掉这道题了(虽然以前听学长讲过LCA 但是很显然。。忘得有点快。。)
以下是 AC 代码
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=40004;
struct node
{
int to,w;
node(int a,int b)
{
to=a;
w=b;
}
};
vector<node>e[maxn];
int f[maxn],dis[maxn],deep[maxn];
int p[maxn][20];
int n,m;
void dfs(int u,int pre,int t)
{
int i;
deep[u]=t;//深度
f[u]=pre;//父节点
for(i=0;i<e[u].size();i++)
{
int v=e[u][i].to;
if(v!=pre)
{
dis[v]=dis[u]+e[u][i].w;//距离根的距离
dfs(v,u,t+1);
}
}
}
void init()
{
//p[i][j]表示i结点的第2^j祖先
int i,j;
for(j=0;(1<<j)<=n;j++)
for(i=1;i<=n;i++)
p[i][j]=-1;
for(i=1;i<=n;i++)
p[i][0]=f[i];
for(j=1;(1<<j)<=n;j++)
for(i=1;i<=n;i++)
if(p[i][j-1]!=-1)
p[i][j]=p[p[i][j-1]][j-1];//i的第2^j祖先就是i的第2^(j-1)祖先的第2^(j-1)祖先
}
int lca(int a,int b)//最近公共祖先
{
int i,j;
if(deep[a]<deep[b])
swap(a,b);
for(i=0;(1<<i)<=deep[a];i++);
i--;
//使a,b两点的深度相同
for(j=i;j>=0;j--)
if(deep[a]-(1<<j)>=deep[b])
a=p[a][j];
if(a==b)
return a;
//倍增法,每次向上进深度2^j,找到最近公共祖先的子结点
for(j=i;j>=0;j--)
{
if(p[a][j]!=-1&&p[a][j]!=p[b][j])
{
a=p[a][j];
b=p[b][j];
}
}
return f[a];
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
e[i].clear();
for(int i=1;i<n;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
e[a].push_back(node(b,c));
e[b].push_back(node(a,c));
}
dis[1]=0;
dfs(1,-1,0);//找到各点的深度和各点的父节点以及距离根的距离
init(); //初始各个点的2^j祖先是谁
for(int i=0;i<m;i++)
{
int a,b,ans;
scanf("%d%d",&a,&b);
ans=dis[a]+dis[b]-2*dis[lca(a,b)];
printf("%d\n",ans);
}
}
return 0;
}