- 题目大意:给定一棵无根树,树边带权,求树上两点的最近距离。
- 思路:典型的LCA问题,采用倍增的方法。这里介绍一下倍增法求LCA。记数组fa[i,j]表示节点i向上走2j步所能达到的点,在给出直接的父子u,v时记录下fa[u,0]=v,此后利用fa[i][j]=fa[fa[i][j-1]][j-1]就能得到其它的值。利用dfs获得各个节点的深度dep[ ]。节点u,v在寻找LCA时,令dep[u]>dep[v],再让u向上直至dep[u]=dep[v],最后二者一起向上找,直至fa[u,0]=fa[v,0],此时停止寻找,则LCA=fa[u,0]。寻找时,令j从大到小枚举,发现fa[u,j]!=fa[v,j]就往上蹦2j步。若要记录花费,只需使用数组g[i,j]表示从节点i向上蹦2j步的费用,很容易得到g[i,0]=c以及g[i][j]=g[fa[i][j-1]][j-1]+g[i][j-1]。
- 注意:这是一个无向图,需要自己选定树根,这里我选的是0,并且需要双向存边,并在DFS中确定树的父子节点关系。
- 代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
struct node
{
int id,len;
node(int id=0,int len=0):id(id),len(len){}
};
const int maxn=50005;
int n,m,costi[maxn],fa[maxn][25],g[maxn][20],dep[maxn];
int ans;
bool vis[maxn];
vector<node> son[maxn];
void build(int root,int depth)
{
dep[root]=depth;
vis[root]=1;
vector<node>::iterator it;
for (it=son[root].begin();it!=son[root].end();++it)
{
if (!vis[(*it).id])
{
fa[(*it).id][0]=root;
g[(*it).id][0]=(*it).len;
build((*it).id,depth+1);
}
}
}
int adjust(int u,int step)
{
for (int j=20;j>=0;--j)
{
if ((1<<j)<=step)
{
ans+=g[u][j];
u=fa[u][j];
step-=(1<<j);
if (step==0)
return u;
}
}
}
void query(int u,int v)
{
ans=0;
if (u==v)
{
printf("0");
return;
}
if (dep[u]<dep[v])
swap(u,v);
if (dep[u]!=dep[v])
u=adjust(u,dep[u]-dep[v]);
for (int j=20;j>=0;--j)
if (fa[u][j]!=fa[v][j])
{
ans+=g[u][j];
ans+=g[v][j];
u=fa[u][j];
v=fa[v][j];
}
if (u!=v)
{
ans+=g[u][0];
ans+=g[v][0];
}
printf("%d\n",ans);
}
void init()
{
scanf("%d",&n);
int u,v,c;
memset(fa,0,sizeof(fa));
memset(vis,0,sizeof(vis));
for (int i=1;i<=n-1;++i)
{
scanf("%d%d%d",&u,&v,&c);
son[u].push_back(node(v,c));
son[v].push_back(node(u,c));
}
build(0,0);
for (int j = 1; j < 20; ++j)
for (int i = 1; i <= n; ++i)
{
fa[i][j]=fa[fa[i][j-1]][j-1];
g[i][j]=g[fa[i][j-1]][j-1]+g[i][j-1];
}
scanf("%d",&m);
while (m--)
{
scanf("%d%d",&u,&v);
query(u,v);
}
}
int main()
{
init();
return 0;
}