题目链接: 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; }