[BZOJ 2125]最短路---仙人掌图+Tarjan缩环+LCA

题目:

给一个N个点M条边的连通无向图,满足每条边最多属于一个环,有Q组询问,每次询问两点之间的最短路径。

Input

输入的第一行包含三个整数,分别表示N和M和Q 下接M行,每行三个整数v,u,w表示一条无向边v-u,长度为w 最后Q行,每行两个整数v,u表示一组询问

Output

输出Q行,每行一个整数表示询问的答案

Sample Input

9 10 2
1 2 1
1 4 1
3 4 1
2 3 1
3 7 1
7 8 2
7 9 2
1 5 3
1 6 4
5 6 1
1 9
5 7

Sample Output

5
6

Hint

对于100%的数据,N<=10000,Q<=10000

为了搞论文去看了仙人掌,然后第一道仙人掌DP经典问题就是这个嘛。。
一遍过是很欣慰的,不然鬼知道会调到什么时候【来自一直查错能力为负的人类】
大致思路想到了,但是实现。。。。/最后果然还是参考了dalao的写法,顿时觉得程序设计真的要命啊。。。
其实思路还是比较简单的:
首先,求任意两点之间的最短路,我们可以类比树上的做法,利用两点的LCA来算——
两点间最短路=两点到根的距离之和-2*LCA到根的距离

问题是,这是一棵可爱的仙人掌,每条边最多属于一个环,不存在自环的无向连通图。

那我们怎么搞呢?

我们可以用Tarjan【%%%】处理每个环,也就是说将环用一个新建的点编号来表示–cir[u]表示以u为父亲的环的新编号;mad[u]表示u这个新编号下组成环的节点
然后处理环上每个点到根节点的距离

但是。问题就是这毕竟不是树,我们的LCA有两种情况:
1.普通节点
2.在环上
注意,第二种情况不是简单意义的在环上,而是这样的:
这里写图片描述
对于第一种情况直接按照以前的方法处理就好
对于第二种,我们可以考虑新建一棵树【注意,这是个树】
也就是说,对于一个环我们定义这个环上深度最小的点为根,将每个环就转换成了——
一个新节点【环的新编号】连向根【为了避免与编号冲突,我们将他+n,后同】,然后这个环除根以外的所有组成节点连向这个新节点
代码中的fa就是这样计算的,因为我们建树只是为LCA服务,计算fa,dep就等同于建了一棵新的树。【否则代码太烦浪费时间,jiao妙】

于是我们在新树上找LCA,如果LCA>n,就说明是普通节点,否则就是环了~
因此如果两个点的LCA是环 ,我们只需要在两端之中选择短的一条即可

最后一个点就是说,关于计算距离这里,看看代码就能理解了;
d[]是map集合咯,d[u][v]表示的就是u这个环里,从这个环的根到v这个点的距离【进栈顺序相同的,顺时针or逆时针】
dis[]就是到根的距离。
size[]是这个环的总长度。【用于最后的两端比较】
最后记得递归处理这个节点上面的点。/虽然一条边不能同时属于两个环,一个节点是可以的。
完结撒花~

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
#define maxn 40010
using namespace std;
int n,m,q;
struct node{
    int v,w,nxt;
}e[maxn];
int head[maxn],cnt;
int fa[maxn][25];
map<int,int>f[maxn];
vector<int>cir[maxn],mad[maxn];//cir[u]表示以u为父亲的环的新编号;mad[u]表示u这个新编号下组成环的节点 
int dfn[maxn],low[maxn],dfs_clock=0;
int st[maxn],stn=0;
int cirn=0;
int dep[maxn];
void add(int u,int v,int w){
    e[++cnt].v=v;
    e[cnt].nxt=head[u];
    head[u]=cnt;
    if(f[u].find(v)==f[u].end()){
        f[u][v]=w;
    }
    else f[u][v]=min(f[u][v],w);
}
void tarjan(int u){
    dfn[u]=low[u]=++dfs_clock;
    st[++stn]=u;
    for(int i=head[u];i!=-1;i=e[i].nxt){
        int v=e[i].v;
        if(dfn[v])//!
            low[u]=min(low[u],dfn[v]);
        else{
            tarjan(v);
            low[u]=min(low[v],low[u]);
            if(dfn[u]==low[v]){
                ++cirn;
                cir[u].push_back(cirn);
                mad[cirn].push_back(u);
                fa[cirn][0]=n+u;
                int id;
                do{
                    id=st[stn--];
                    mad[cirn].push_back(id);
                    fa[n+id][0]=cirn; //printf("[%d]\n",id);
                }while(id!=v);//!
            }
        }
    }
}
int getdep(int x){
    if(fa[x][0]==0)dep[x]=1;
    if(dep[x])return dep[x];
    return dep[x]=getdep(fa[x][0])+1;
}
void init(){
    memset(dep,0,sizeof dep);
    for(int i=1;i<=20;i++){
        for(int j=1;j<=cirn;j++) fa[j][i]=fa[fa[j][i-1]][i-1];
        for(int j=n+1;j<=n*2;j++) fa[j][i]=fa[fa[j][i-1]][i-1];
    }
    for(int j=1;j<=cirn;j++) getdep(j);
    for(int j=n+1;j<=n*2;j++) getdep(j);
}
int tx,ty;
int getlca(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    for(int j=20;j>=0;j--){
        if(dep[fa[x][j]]>=dep[y])x=fa[x][j];
    }
    if(x==y)return x;
    for(int j=20;j>=0;j--){
        if(fa[x][j]!=fa[y][j]){
            x=fa[x][j];y=fa[y][j];
        }
    }
    tx=x;ty=y;
    return fa[x][0];
}
int dis[maxn];
map<int,int>d[maxn];
int size[maxn];
void prepare(int x){
    stn=0;
    for(int i=0;i<mad[x].size();i++)st[++stn]=mad[x][i];
    st[++stn]=mad[x][0];
    for(int i=1;i<stn;i++){
        size[x]+=f[st[i]][st[i+1]];
        if(i!=stn-1){
            d[x][st[i+1]]=d[x][st[i]]+f[st[i]][st[i+1]];
        }
    }
    int l=2,r=stn-1;
    while(l<=r){
        int tp1=dis[st[l-1]]+f[st[l-1]][st[l]];
        int tp2=dis[st[r+1]]+f[st[r+1]][st[r]];
        if(tp1<tp2){
            dis[st[l]]=tp1;l++;
        }
        else{
            dis[st[r]]=tp2;r--;
        }
    }
    for(int i=1;i<mad[x].size();i++){
        for(int j=0;j<cir[mad[x][i]].size();j++)
            prepare(cir[mad[x][i]][j]);
    } 
}
int main(){
    scanf("%d%d%d",&n,&m,&q);
    memset(head,-1,sizeof head);
    for(int i=1;i<=m;i++){
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);add(v,u,w);
    }
    tarjan(1);init();
    for(int i=0;i<cir[1].size();i++)
        prepare(cir[1][i]);
    while(q--){
        int x,y;
        scanf("%d%d",&x,&y);
        int lca=getlca(n+x,n+y);
        if(lca>n)printf("%d\n",dis[x]+dis[y]-2*dis[lca-n]);
        else{
            int ans=dis[x]+dis[y]-dis[tx-n]-dis[ty-n];
            int tmp=abs(d[lca][tx-n]-d[lca][ty-n]);
            ans+=min(tmp,size[lca]-tmp);
            printf("%d\n",ans);
        }
    }
    return 0;
}
/*
9 10 1
1 2 1
1 4 1
3 4 1
2 3 1
3 7 1
7 8 2
7 9 2
1 5 3
1 6 4
5 6 1
8 9
*/
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值