题目大意:给你n个节点的森林(注意不是一棵树),m条路径的长度,c个询问,要你回答每个询问两个点i和j的最短距离,或者回答没有连接。
解题思路:对于每个节点,求出其到根的距离a[i],然后求两点的最近公共祖先LCA,最后的答案为$a[i]+a[j]-2*a[LCA(i,j)]$。
我用的是Tarjan算法。
注意此题可能出现两个相同点的情况,需要注意一些细节,或者特判。
C++ Code:
#include<cstdio>
#include<cstring>
using namespace std;
int n,m,c,cnt=0,cntq=0,head[10002],headq[1000002],fa[10002],a[10002]={0};
bool vis[10002],all[10002];
struct edge{
int to,dist,next;
}e[20004];
struct Q{
int to,next;
}q[2000004];
int ans[1000002];
void addedge(int from,int to,int dist){
cnt++;
e[cnt].to=to;
e[cnt].dist=dist;
e[cnt].next=head[from];
head[from]=cnt;
cnt++;
e[cnt].to=from;
e[cnt].dist=dist;
e[cnt].next=head[to];
head[to]=cnt;
}
int dad(int x){
return(fa[x]==x)?(x):(fa[x]=dad(fa[x]));
}
void addq(int x,int y){
cntq++;
q[cntq].to=y;
q[cntq].next=headq[x];
headq[x]=cntq;
cntq++;
q[cntq].to=x;
q[cntq].next=headq[y];
headq[y]=cntq;
}
void lca(int root,int pre){
fa[root]=root;
for(int i=head[root];i;i=e[i].next){
int p=e[i].to;
if(p!=pre){
a[p]=a[root]+e[i].dist;
lca(p,root);
}
}
vis[root]=all[root]=true;
for(int i=headq[root];i;i=q[i].next){
int p=q[i].to;
if(vis[p]){
int LCA=dad(p);
ans[(i+1)/2]=a[root]+a[p]-a[LCA]*2;
}
}
fa[root]=pre;
}
int main(){
while(scanf("%d%d%d",&n,&m,&c)!=EOF){
cnt=cntq=0;
memset(a,0,sizeof(a));
memset(fa,0,sizeof(fa));
memset(head,0,sizeof(head));
memset(headq,0,sizeof(headq));
memset(e,0,sizeof(e));
memset(q,0,sizeof(q));
memset(ans,-1,sizeof(ans));
memset(all,0,sizeof(all));
for(int i=1;i<=m;i++){
int from,to,dist;
scanf("%d%d%d",&from,&to,&dist);
addedge(from,to,dist);
}
for(int i=1;i<=c;i++){
int x,y;
scanf("%d%d",&x,&y);
addq(x,y);
}
for(int i=1;i<=n;i++)
if(!all[i]){
memset(vis,0,sizeof(vis));
lca(i,0);
}
for(int i=1;i<=c;i++)
if(ans[i]==-1)puts("Not connected");else
printf("%d\n",ans[i]);
}
return 0;
}