【JZOJ1914】【2011集训队出题】最短路

题目大意
给你一个带权无向图,满足图上任意一条边最多属于一个环,有\(q\)个询问,求\(u,v\)之间的最短路。
\(n,q\leq 10000\)

Solution

首先用Tarjan建一棵以\(1\)为根的搜索树,找出每个环,记录环的总长,将环内每个点\(u\)连向环内\(dfs\)序最小的点\(v\),边权为\(u\)\(v\)的最短路,然后把不在环上的边照旧连上,这样我们就得到了一棵树。
现在要求\(a\)\(b\)的最短路,我们考虑倍增,若两个点在一条链上,它们的最短路就是树上的距离。若两个点不在一条链上,设\(u\)\(v\)分别为\(a\)\(b\)\(lca\)的链上\(lca\)下面那个点,若\(u,v\)在原图中不在一个环,\(a,b\)的最短路就是它们在树上的距离,若\(u,v\)在原图中在一个环,则\(u\)\(v\)有两种走法,我们要取最小值,然后再加上\(a\)\(u\)的距离和\(b\)\(v\)的距离。
这里有个小技巧,我们可以先在原图上从\(1\)开始求出\(1\)到每个点的最短路\(dis[i]\),那么将环上每个点\(u\)连向\(dfs\)序最小的点\(v\)时,边权就是\(dis[u]-dis[v]\),后面求树上两点距离时也是用\(dis\)数组求。

Code

#include
#include
#include
#include
using namespace std;

typedef long long ll;
const int N=10007,M=600007;
ll Abs(ll a){return a>0?a:-a;}

int n,m,q;

int tot,cnt,st[N],to[M],nx[M],dep[N],col[N];
ll len[M],dis[N],sum[N],totlen[N];
vector son[N];
void add(int u,int v,ll w){to[++tot]=v,nx[tot]=st[u],len[tot]=w,st[u]=tot;}

int head,tail,que[M],vis[N];
void spfa(){
    memset(dis,0x3f,sizeof(dis));memset(vis,0,sizeof(vis));
    head=1,que[tail=1]=1,dis[1]=0,vis[1]=1;
    while(head<=tail){
        int u=que[head++];vis[u]=0;
        for(int i=st[u];i;i=nx[i])if(dis[u]+len[i]=0;--i)if(dep[anc[u][i]]>=dep[v])u=anc[u][i];
    if(u==v)return dis[a]-dis[b];
    for(int i=14;i>=0;--i)if(anc[u][i]!=anc[v][i])u=anc[u][i],v=anc[v][i];
    if(col[u]&&col[u]==col[v])return dis[a]-dis[u]+dis[b]-dis[v]+min(Abs(sum[u]-sum[v]),totlen[col[u]]-Abs(sum[u]-sum[v]));
    return dis[a]+dis[b]-2*dis[anc[u][0]];
}

int main(){
    scanf("%d%d%d",&n,&m,&q);
    for(int i=1;i<=m;++i){
        int u,v;ll w;
        scanf("%d%d%lld",&u,&v,&w),add(u,v,w),add(v,u,w);
    }
    spfa();
    dfs(1,1);
    dep[1]=1,dfs1(1);
    for(int j=1;j<=14;++j)for(int i=1;i<=n;++i)anc[i][j]=anc[anc[i][j-1]][j-1];
    while(q--){
        int u,v;
        scanf("%d%d",&u,&v);
        printf("%lld\n",qry(u,v));
    }
    return 0;
}

你可能感兴趣的:(【JZOJ1914】【2011集训队出题】最短路)