第一次做LCA的题目。
题意就是在一棵树中, 询问每两点的距离。
开始一看觉得用带权并查集就可以了,但是只能求出相对距离,无法判断其具体位置,所以需要找到共同祖先,所以在tarjan函数中就有了两个循环, 一个是类似种类并查集,一个是离线询问,找到最近公共祖先。
可以根据我代码下方的数据调试了一下, 很快就懂了。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int MAXN = 40005;
struct node
{
int u, v, w, lca, from;
} edge[MAXN*2], edge1[MAXN*2];
int head[MAXN];
int head1[MAXN];
int n, m;
int cnt, cnt1;
int bin[MAXN];
int vis[MAXN];
int dir[MAXN];
int ance[MAXN];
void init()
{
memset(vis, 0, sizeof(vis));
memset(dir, 0, sizeof(dir));
memset(head, -1,sizeof(head));
memset(head1, -1, sizeof(head1));
cnt=cnt1=0;
}
void add_edge(int u, int v, int w)
{
edge[cnt].v=v;
edge[cnt].w=w;
edge[cnt].from=head[u];
head[u]=cnt++;
}
void add_edge1(int u, int v)
{
edge1[cnt1].u=u;
edge1[cnt1].v=v;
edge1[cnt1].lca=-1;
edge1[cnt1].from=head1[u];
head1[u]=cnt1++;
}
int findx(int x)
{
if(bin[x]==x)
return x;
return bin[x]=findx(bin[x]);
}
void Merge(int u, int v)
{
int x=findx(u);
int y=findx(v);
if(x!=y)
bin[y]=x;
}
void tarjan(int u)
{
vis[u]=1;
ance[u]=bin[u]=u;//相当于并查集的初始化
for(int i=head[u]; ~i; i=edge[i].from)
{
int v=edge[i].v;
int w=edge[i].w;
if(!vis[v])
{
dir[v]=dir[u]+w;//种类并查集的操作
tarjan(v);//往子树中递归,求出每个子树里的节点距离根节点1的距离
Merge(u,v);//并查集合并,路径压缩
}
}
for(int i=head1[u]; ~i; i=edge1[i].from)//离线操作,存下所有的询问,找出每次询问的最近公共祖先
{
int v=edge1[i].v;
if(vis[v])
{
edge1[i].lca=edge1[i^1].lca=ance[findx(v)];//如果访问过,就可以找出公共祖先。
}
}
}
int main()
{
int t;
scanf("%d", &t);
while(t--)
{
init();
scanf("%d %d", &n, &m);
// for(int i=1;i<=n;++i)
// bin[i]=i;
for(int i=1; i<n; ++i)
{
int u, v, w;
scanf("%d %d %d", &u, &v, &w);
add_edge(u, v, w);
add_edge(v, u, w);
}
for(int i=0; i<m; ++i)
{
int u, v;
scanf("%d %d", &u, &v);
add_edge1(u, v);
add_edge1(v, u);
}
dir[1]=0;
tarjan(1);
// for(int i=1;i<=n;++i)
// {
// printf("i=%d | bin = %d ance = %d dir = %d\n", i, bin[i], ance[i], dir[i]);
// }
// for(int i=0;i<2*m;++i)
// {
// printf("i=%d u=%d v=%d lca=%d\n", i, edge1[i].u, edge1[i].v, edge1[i].lca);
// }
for(int i=0; i<m; ++i)
{
int s=i*2, u=edge1[s].u, v=edge1[s].v,lca=edge1[s].lca;
printf("%d\n", dir[u]+dir[v]-2*dir[lca]);
}
}
return 0;
}
/*
10
8 7
1 2 10
1 3 15
2 4 20
5 2 30
6 4 40
3 7 45
3 8 50
3 2
4 5
3 4
2 3
3 5
6 4
8 6
*/
/* 1
2 3
4 5 7 8
6
在每一次求出相对距离的时候,走到叶子结点时,求一次公共祖先,分为两种情况, 访问过和未访问过,未访问就不管,访问过的话,那么对于当前u和询问v的公共祖先,就是v当前的祖先。
在 1
3
7 8
走到3,之后会判断7和8,在询问中的和另一个被询问的点的公共祖先,此时就是3。
*/