题目链接: hdu 2874
题目大意: 在有权值森林中,任意查询两个结点的最短距离
若两点不联通则输出Not connected
解题思路: 用并查集把边连接的两个结点合并
查询的时候先判断两点是否在同一个联通图,不联通则直接输出
如何求联通块内任意结点间的距离呢?Floyd 一万个顶点O(n^3)肯定爆掉了
在一棵树中,假设某点K为根结点,数组dist[ i ]存储i点到达根结点的距离
因为是树,所以这个距离唯一且最短
画一画dist[ a ]和dist[ b ]的路径发现它们有重合的部分x
从顶点a到顶点b的距离是L,则L+2x=dist[ a ]+dist[ b ]
再仔细想一想发现x即是a和b最近公共祖先到根结点的距离dist[ LCA(a,b) ]
先确定dist[ ]的值,然后离线查找最近公共祖先既可求出最短距离,时间复杂度O(n+m)
代码:
//Final LCA离线查找 两点最短距离
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX 10100
struct {
int to,w,next;
}Hash[MAX*3],Qes[MAX*200]; //Hash是存在的边,Qes是需要查询点
int n,Index1,Index2,parent[MAX],ansetor[MAX],pre1[MAX],pre2[MAX],answer[MAX*200];
int visit[MAX],dist[MAX];
void Add_edge(int a,int b,int w) //建立树
{
Hash[Index1].to=b; Hash[Index1].w=w;
Hash[Index1].next=pre1[a];
pre1[a]=Index1++;
}
void Add_Qes(int a,int b,int w) //建立离线查询
{
Qes[Index2].to=b; Qes[Index2].w=w;
Qes[Index2].next=pre2[a];
pre2[a]=Index2++;
}
void Init(int n) //parent区分不同的联通块,ansetor在线更新最近祖先
{
for(int i=1;i<=n;i++)
parent[i]=ansetor[i]=i;
}
int Find(int x) //区分联通块
{
int s,j;
s=x;
while(x!=parent[x])
x=parent[x];
while(s!=x)
{
j=parent[s];
parent[s]=x;
s=j;
}
return x;
}
void Union(int r1,int r2) //区分联通块
{
int R1,R2;
R1=Find(r1);
R2=Find(r2);
if(R1!=R2)
parent[R1]=R2;
}
int Find2(int x) //最近公共祖先查找
{
int s,j;
s=x;
while(x!=ansetor[x])
x=ansetor[x];
while(s!=x)
{
j=ansetor[s];
ansetor[s]=x;
s=j;
}
return x;
}
void LCA(int u) //离线查找最近公共祖先,并且在线更新当前点到根结点的距离
{
int i,v;
visit[u]=1;
ansetor[u]=u;
for(i=pre1[u];i!=-1;i=Hash[i].next)
{
v=Hash[i].to;
if(!visit[v]) //如果此点没有访问过,则更新dist[]
{
dist[v]=dist[u]+Hash[i].w;
LCA(v); //回溯
ansetor[v]=u; //当前点的祖先暂时是u
}
}
for(i=pre2[u];i!=-1;i=Qes[i].next)
{
v=Qes[i].to;
if(visit[v]) //如果这个点之前访问过,那么它最近的祖先也就确定了
{
answer[Qes[i].w]=dist[u]+dist[v]-dist[ansetor[Find2(v)]]*2;
}
}
}
int main()
{
int i,m,Q,a,b,c;
while(scanf("%d%d%d",&n,&m,&Q)!=EOF)
{
Init(n);
Index1=Index2=0;
memset(dist,0,sizeof(dist));
memset(pre1,-1,sizeof(pre1));
memset(pre2,-1,sizeof(pre2));
memset(visit,0,sizeof(visit));
for(i=0;i<m;i++)
{
scanf("%d%d%d",&a,&b,&c);
Add_edge(a,b,c); //**如果树有结构的才用fathernum[]
Add_edge(b,a,c); //**
Union(a,b);
}
memset(answer,-1,sizeof(answer));
for(i=1;i<=Q;i++)
{
scanf("%d%d",&a,&b);
if(Find(a)==Find(b)) //存在边的点并入一个联通快
{
Add_Qes(a,b,i);
Add_Qes(b,a,i);
}
}
for(i=1;i<=n;i++)
{
if(!visit[i]) //**没有访问过的点开始访问
LCA(i);
}
for(i=1;i<=Q;i++)
{
if(answer[i]==-1)
printf("Not connected\n");
else
printf("%d\n",answer[i]);
}
}
return 0;
}