仙人掌缩环求最短路Demo

1) 求出起点1到所有点的最短路,这其实就是缩环后的树的distanceToRoot.
2) O(n) 找环,将环内所有点向根连边,并标记删除环边。 (将时间均摊到儿子中,每个点在各个环中最多做一次非环顶。于是每一个环时间其实就是点数和)
3) lca时若lca下一层的两点u,v在同一环内,则分类讨论环内距离。
code by alan_cty (Orz)

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define rep(i,a) for(int i=last[a];i;i=next[i])
#define N 22005
using namespace std;
int n,m,x,y,z,l,q,tot;
int d[N],c[N],dfn[N],dis[N],cir[N],p[N],len[N];
int t[N*2],v[N*2],next[N*2],last[N],fa[N][17],que[N*5];
bool e[N*2],bz[N];
//e[i]=1,则该边是环边
void add(int x,int y,int z) {
    t[++l]=y;v[l]=z;next[l]=last[x];last[x]=l;
}
void spfa() {
    memset(dis,127,sizeof(dis));dis[1]=0;
    int i=0,j=1;bz[1]=1;que[1]=1;
    while (i<j) {
        rep(k,que[++i]) if (dis[t[k]]>dis[que[i]]+v[k]) {
            dis[t[k]]=dis[que[i]]+v[k];
            if (!bz[t[k]]) bz[t[k]]=1,que[++j]=t[k];
        }
        bz[que[i]]=0;
    }
}
void cate(int S,int T,int line) {
    e[line]=e[line^1]=1;c[++c[0]]=v[line];
    for(int i=T;i!=S;i=t[p[i]^1]) {
        cir[i]=c[0];e[p[i]]=e[p[i]^1]=1;
        c[c[0]]+=v[p[i]];add(S,i,0);add(i,S,0);
    }
}
void dfs(int x) {
    dfn[x]=++tot;
    rep(i,x) if (i<=2*m+1&&i!=(p[x]^1))
        if (!dfn[t[i]]) {
            p[t[i]]=i;
            len[t[i]]=len[x]+v[i];
            dfs(t[i]);
        } else if (dfn[t[i]]<dfn[x]) cate(t[i],x,i);
}
void make(int x,int y) {
    d[x]=d[y]+1;fa[x][0]=y;
    rep(i,x) if (t[i]!=y&&!e[i]) make(t[i],x);
} 
int lca(int &x,int &y) {
    if (d[x]<d[y]) swap(x,y);
    fd(j,16,0) if (d[fa[x][j]]>=d[y]) x=fa[x][j];
    if (x==y) return x;
    fd(j,16,0) if (fa[x][j]!=fa[y][j]) x=fa[x][j],y=fa[y][j];
    return fa[x][0];
} 
int main() {
    scanf("%d%d%d",&n,&m,&q);l=1;
    fo(i,1,m) scanf("%d%d%d",&x,&y,&z),add(x,y,z),add(y,x,z);
    spfa();
    dfs(1);make(1,0);
    fo(j,1,16) fo(i,1,n) fa[i][j]=fa[fa[i][j-1]][j-1];
    for(;q;q--) {
        scanf("%d%d",&x,&y);int a=x,b=y;z=lca(x,y);
        if (cir[x]&&cir[y]&&cir[x]==cir[y]) {
            z=abs(len[x]-len[y]);
            printf("%d\n",dis[a]-dis[x]+dis[b]-dis[y]+min(z,c[cir[x]]-z));
            continue;
        }
        printf("%d\n",dis[a]+dis[b]-2*dis[z]);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值