仙人掌最短路--bzoj2125

与基环树最短路的思想相同
我们只需要对每个子树求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;
}

猜你喜欢

转载自blog.csdn.net/sizeof_you/article/details/80945979