BZOJ 2125 最短路 (拆环重建图 lca spfa)

2125: 最短路

Time Limit: 1 Sec Memory Limit: 259 MB

Description

给一个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

思路:

每个点有两个值,一个是从根到这个点的最短路d[i],一个是从根沿dfs树到这个点的距离rd[i].

之后是一个很牛逼的建图,把环上的点都连到环中深度最浅的点得到一颗树,并维护每个点所在的环以及每个环的环长。

对于一个询问(x,y),假设dep[x]>dep[y],分情况:

1.如果x和y的lca是y,那么答案为d[x]-d[y].

2.如果x和y的lca没在环里,那么答案为d[x]+d[y]-2*d[lca].

3.如果x和y的lca在环里,设x,y向上走遇到的第一个环上节点分别是a,b,那么答案为d[x]-d[a]+d[y]-d[b]+min(abs(rd[a]-rd[b]),环长-abs(rd[a]-rd[b])).

不知道什么仙人掌,乱搞吧。。。
方法就像上面大佬的题解一样,辣基来解释一下。。。
我们显然可以建出一棵树,然后还有一些非树边。
考虑到由于有这些非树边,我们的选择就可能很混乱,往上往下,跳与不跳都有可能,于是我们考虑统一一下方向,让我们的操作更有规律。所以我们把环上的点都连到环中深度最浅的点得到一颗树,并维护每个点所在的环以及每个环的环长。就是代码里rebuild的部分(虽然改了图,但是保存了原图的环信息,总权值wgh,从标志点(环中最浅)单方向到每个点的环长rd等等)
这里写图片描述
之后对于深度较深的一个点u就只能向上走了。因为如果向下走不能跳出u的子树的话,显然是不优的。而如果可以跳出,就说明u一定在一个环中,那么它在新树中也是不会向下走的。不走环就走fa,走环就走兄弟节点。
我们提前用spfa维护了原图上的最短路d,我们讨论x,y两个点在新树上的位置关系。
假设dep[x]>dep[y](1为root)
1.如果x和y的lca是y,那么答案为d[x]-d[y].
因为x只能到兄弟或者父亲,所以x到1的路径上(不管哪条)都会经过y。
2.如果x和y的lca没在环里,那么答案为d[x]+d[y]-2*d[lca].
因为在x,y走向lca的过程中,这两个点一定不会在同一个环中。(否则lca也一定在环中)也就是说不管怎么走,x和y之间的所有路径一定会经过lca。所以ans就是x到lca的d加上y到lca的d。
3.如果x和y的lca在环里,设x,y向上走遇到的第一个环上节点分别是a,b,那么答案为d[x]-d[a]+d[y]-d[b]+min(abs(rd[a]-rd[b]),环长-abs(rd[a]-rd[b])).
与上一种情况相反(到a,b之前是一样的),到a,b之后我们就有两条路径可以选择了(环上)。
这种建树方法的好处第一就是破环,方便我们判断两点在原图上的位置关系。第二就是优化上述第3种情况,因为同一个环上的点dep是一样的,所以可以直接倍增的向上跳找到a和b。

#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
#define N 50010
#define M 100010
using namespace std;

const int P = 13;
int n, m, q, idc=1, idx, cnt;
int head[N], del[M], dfn[N], st[N], dis[N], vis[N], rd[N], dep[N], place[N], wgh[N], acc[N][P+1];

inline int read(){
    int x = 0, f = 1; char ch = getchar();
    while ( ch < '0' || ch > '9' ) { if(ch == '-') f = -1; ch = getchar(); }
    while ( ch >= '0' && ch <= '9' ) { x = x * 10 + ch - '0'; ch = getchar(); }
    return x * f;
}

struct Edge{
    int to, nxt, w;
}ed[M << 1];

inline void adde(int u, int vis, int w){
    ed[++idc].to = vis; 
    ed[idc].nxt = head[u];
    ed[idc].w = w;
    head[u] = idc;
}

void spfa() {
    memset(dis, 0x3f, sizeof(dis) );
    queue<int> q; q.push(1), dis[1] = 0;
    while ( !q.empty() ) {
        int u = q.front(); q.pop();
        vis[u] = 0;
        for(int i=head[u]; i; i=ed[i].nxt) {
            int v = ed[i].to;
            if(dis[v] > dis[u] + ed[i].w) {
                dis[v] = dis[u] + ed[i].w;
                if( !vis[v] ) {
                    vis[v] = 1;
                    q.push( v );
                }
            }
        }
    }
}

void rebuild(int x, int y) {//建图 
    if(x == y) return;
    place[x] = cnt;
    adde(y, x, 0); adde(x, y, 0);//
    del[st[x]] = del[st[x]^1] = 1;
    wgh[cnt] += ed[st[x]].w;
    rebuild(ed[st[x]^1].to, y); 
}

void dfs(int u) {
    dfn[u] = ++idx;
    for(int i=head[u]; i; i=ed[i].nxt) {
        int v = ed[i].to;
        if(i!=(st[u]^1) && i<=idc) {
            if( !dfn[v] ) {
                rd[v] = rd[u] + ed[i].w;
                st[v] = i;
                dfs( v );
            }
            else if(dfn[v] < dfn[u]) {
                wgh[++cnt] = ed[i].w;//环的总权值 
                rebuild(u, v);
            }
        }
    }
}

void dfs2(int u, int f) {
    acc[u][0] = f;
    for(int i=1; i<=P; i++)
        acc[u][i] = acc[acc[u][i-1]][i-1];
    for(int i=head[u]; i; i=ed[i].nxt) {
        int v = ed[i].to;
        if(!del[i] && !dep[v]) {
            dep[v] = dep[u] + 1;
            dfs2(v, u);
        }
    }
}

int query(int u, int v) {
    if(dep[u] < dep[v]) swap(u, v);
    int a = u , b = v;
    for(int i=P; i>=0; i--) if(dep[acc[u][i]] >= dep[v]) u = acc[u][i];
    if(u == v) return dis[a] - dis[b];
    for(int i=P; i>=0; i--) if(acc[u][i] ^ acc[v][i]) u = acc[u][i], v = acc[v][i];
    if(place[u] && place[u] == place[v]) {
        int S = abs(rd[u] - rd[v]);
        return dis[a] - dis[u] + dis[b] - dis[v] + min(S, wgh[place[u]]-S);
    }
    return dis[a] + dis[b] - 2 * dis[acc[u][0]];
}

int main() {
    scanf("%d%d%d", &n, &m, &q);
    for(register int i=1; i<=m; i++){
        int u = read(), v = read(), w = read();
        adde(u, v, w); adde(v, u, w);
    }
    spfa(), dfs( 1 ), dep[1] = 1, dfs2(1, 1);
    while ( q-- ) {
        int u = read(), v = read();
        printf("%d\n", query(u, v));
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值