[BZOJ2125]最短路
2125: 最短路
Time Limit: 1 Sec Memory Limit: 259 MB Submit: 985 Solved: 411 [Submit][Status][Discuss]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
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
6
HINT
对于100%的数据,N<=10000,Q<=10000
学习了一波圆方树,大概做法就是把在原仙人掌上的点设为圆点,然后对于每个环,把这个环上除了环顶的所有点重新连在一个方点下面,边权为到环顶的最短距离,方点连在环顶下,边权为0,其它的点该怎么连就怎么连,可以证明这样构成的树是和原仙人掌等价的
那么求两点最短距离就可以搬用树上两点距离的做法,先求出lca。如果lca是圆点,那么直接就是路径上边权和,如果lca是方点,路径肯定还包含了环上的一段,那么就找到lca下方的两个点,根据环的总长度以及两个点分别到环顶的距离和最短距离方向可算出环上的一段
求环用Tarjan的点双算法,求lca用的倍增,感觉这样好找lca下方的两点
#include <cstdio> #include <algorithm> using namespace std; char buf[10000000], *ptr = buf - 1; inline int readint(){ int n = 0; char ch = *++ptr; while(ch < '0' || ch > '9') ch = *++ptr; while(ch <= '9' && ch >= '0'){ n = (n << 1) + (n << 3) + ch - '0'; ch = *++ptr; } return n; } const int maxn = 20000 + 10, maxm = 50000 + 10; struct Edge{ int to, val, next; Edge(){} Edge(int _t, int _v, int _n){ to = _t; val = _v; next = _n; } }e[maxm]; int fir[maxn] = {0}, Fir[maxn] = {0}, cnt = 1; inline void ins(int u, int v, int w){ e[++cnt] = Edge(v, w, fir[u]); fir[u] = cnt; e[++cnt] = Edge(u, w, fir[v]); fir[v] = cnt; } inline void link(int u, int v, int w){ e[++cnt] = Edge(v, w, Fir[u]); Fir[u] = cnt; } int n, m, q, N; int dfn[maxn] = {0}, low[maxn], Index = 0; int sta[maxn], top = 0; int tmp[maxn], dir[maxn], val[maxn], tot[maxn]; void tarjan(int u, int f){ dfn[u] = low[u] = ++Index; sta[++top] = u; for(int v, i = fir[u]; i; i = e[i].next){ v = e[i].to; if(!dfn[v]){ val[v] = e[i].val; tarjan(v, i ^ 1); low[u] = min(low[u], low[v]); if(low[v] > dfn[u]){ link(u, v, e[i].val); top--; } else if(low[v] == dfn[u]){ link(u, ++N, 0); int c = 0, ss; do{ tmp[++c] = sta[top]; }while(sta[top--] != v); for(int j = fir[tmp[1]]; j; j = e[j].next) if(e[j].to == u){ ss = tot[N] = e[j].val; break; } for(int j = 1; j <= c; j++) tot[N] += val[tmp[j]]; for(int j = 1; j <= c; j++){ if(ss < tot[N] - ss){ link(N, tmp[j], ss); dir[tmp[j]] = 1; } else{ link(N, tmp[j], tot[N] - ss); dir[tmp[j]] = 2; } ss += val[tmp[j]]; } } } else if(i ^ f) low[u] = min(low[u], dfn[v]); } } int dep[maxn], fa[maxn][16], road[maxn][16]; void dfs(int u){ for(int t = 1; (1 << t) <= dep[u]; t++){ fa[u][t] = fa[fa[u][t - 1]][t - 1]; road[u][t] = road[u][t - 1] + road[fa[u][t - 1]][t - 1]; } for(int v, i = fir[u]; i; i = e[i].next){ v = e[i].to; fa[v][0] = u; road[v][0] = e[i].val; dep[v] = dep[u] + 1; dfs(v); } } inline int Query(){ int x = readint(); int y = readint(); if(dep[x] < dep[y]) swap(x, y); int d = dep[x] - dep[y], ans = 0; for(int t = 0; t < 16; t++) if(d & (1 << t)){ ans += road[x][t]; x = fa[x][t]; } if(x == y) return ans; for(int t = 15; ~t; t--) if(fa[x][t] != fa[y][t]){ ans += road[x][t] + road[y][t]; x = fa[x][t]; y = fa[y][t]; } if(fa[x][0] <= n) return ans + road[x][0] + road[y][0]; else if(dir[x] == dir[y]) return ans + abs(road[x][0] - road[y][0]); else return ans + min(road[x][0] + road[y][0], tot[fa[x][0]] - road[x][0] - road[y][0]); } int main(){ fread(buf, sizeof(char), sizeof(buf), stdin); N = n = readint(); m = readint(); q = readint(); for(int u, v, w, i = 1; i <= m; i++){ u = readint(); v = readint(); w = readint(); ins(u, v, w); } tarjan(1, 0); for(int i = 1; i <= N; i++) fir[i] = Fir[i]; dep[1] = 0; dfs(1); for(int i = 1; i <= q; i++) printf("%d\n", Query()); return 0; }