与基环树最短路的思想相同
我们只需要对每个子树求lca,在环上我们以d数组记录一个同向的前缀路径长度
可以方便的算出环上两点之间的最短距离,也就是环两侧距离取min,具体看注释
当我们求x与y之间的距离时
可以分类讨论:
1.如果x和y的lca是y或x
2.如果x和y的lca没在环里
3.如果x和y的lca在环里
算法实现:
设1为根
第一步:spfa求到根的最短路dis
第二步:dfs找环,重新建图,断开环上的边,将环上的点连向环的最高点
第三步:bfs计算子树的深度dep和lca用的f数组
第四步:求lca顺便求出两点距离
具体实现还看代码及注释:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
#define maxn 10010
#define maxm 80010
using namespace std;
int n,m,dep[maxn],fa[maxn],q,now;
int cnt=1,head[maxn],dis[maxn],len[maxn],tot;
int dfn[maxn],num,f[maxn][16],d[maxn],g[maxn];
bool vis[maxn],del[maxm];//我不会告诉你我f数组第二维开成了1<<15 tle了七八回
struct EDGE{
int to,nxt,frm,w;
}edge[maxm];
void add(int x,int y,int z){
cnt++;
edge[cnt].to=y;
edge[cnt].nxt=head[x];
edge[cnt].w=z;
head[x]=cnt;
}
// 建图,断边,将环上的点与最高点连一条0权边
void circle(int x,int e){
int y=x,i; x=edge[e].to;
len[++tot]=edge[e].w; g[y]=tot;
del[e]=del[e^1]=1; add(x,y,0); add(y,x,0);//del为断边操作
for(i=fa[y];(y=edge[i^1].to)!=x;i=fa[y]){
len[tot]+=edge[i].w; g[y]=tot;
del[i]=del[i^1]=1; add(x,y,0); add(y,x,0);
}
len[tot]+=edge[i].w;
}
//dfs用dfs序找环
void dfs(int x){
dfn[x]=++num;
for(int i=head[x];i;i=edge[i].nxt){
if(i>now) continue;//这里如果是后加进来的边说明是环上被处理过的,不管他
int v=edge[i].to;
if(!dfn[v]){
fa[v]=i;//fa存的是通往这个点的边
d[v]=d[x]+edge[i].w;//d数组是环上的点到环的最高点的距离
dfs(v);
}
else if(fa[x]!=(i^1) && dfn[v]<dfn[x]) circle(x,i);
}//如果u的dfs序比v大说明找到了环
}
//单源最短路
void spfa(int s){
queue<int> qq;
memset(dis,0x3f,sizeof dis);
qq.push(s); vis[s]=1; dis[s]=0;
while(!qq.empty()){
int u=qq.front(); qq.pop();
vis[u]=0;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(dis[v]>dis[u]+edge[i].w){
dis[v]=dis[u]+edge[i].w;
if(!vis[v]) qq.push(v),vis[v]=1;
}
}
}
}
//bfs求深度和lca的f数组预处理
void bfs(){
queue<int> qq; while(!qq.empty()) qq.pop();
memset(vis,0,sizeof vis);
qq.push(1); vis[1]=1; dep[1]=1;
while(!qq.empty()){
int u=qq.front(); qq.pop();
for(int i=head[u];i;i=edge[i].nxt){
if(del[i]) continue;//如果这条边断开了就不再搜,相当于只处理子树的lca
int v=edge[i].to;
if(!vis[v]){
dep[v]=dep[u]+1; f[v][0]=u;
for(int j=1;j<15;j++) f[v][j]=f[f[v][j-1]][j-1];
qq.push(v); vis[v]=1;
}
}
}
}
//求lca
int lca(int x,int y){
if(dep[x]<dep[y]) swap(x,y);
int p=x,q=y;
for(int i=14;i>=0;i--)
if(dep[f[x][i]]>=dep[y]) x=f[x][i];//常规操作
if(x==y) return dis[p]-dis[q];//在一条链上
for(int i=14;i>=0;i--)
if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
if(g[x]&&g[x]==g[y]) //lca在环上,则环两侧取min
{int ww=abs(d[x]-d[y]);return dis[p]-dis[x]+dis[q]-dis[y]+min(ww,len[g[x]]-ww);}
return dis[p]+dis[q]-2*dis[f[x][0]];//不在环上也不在同一条链上
}
int main(){
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=m;i++){
int x, y, z; scanf("%d%d%d",&x,&y,&z);
add(x,y,z); add(y,x,z);
}
spfa(1);//第一步以1为根求最短路
now=cnt;//这里是记录一下初始时边的编号到哪儿
dfs(1);//以1出发找环
bfs();//bfs求深度和f数组
for(int i=1;i<=q;i++){
int x,y; scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));//lca
}
return 0;
}