BZOJ2125: 最短路(圓方樹)
阿新 • • 發佈:2018-06-13
%d mes 位置 namespace tdi 分類討論 pac line -s Time Limit: 1 Sec Memory Limit: 259 MB
Submit: 1574 Solved: 651
[Submit][Status][Discuss]
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
6
Submit: 1574 Solved: 651
[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
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
56
HINT
對於100%的數據,N<=10000,Q<=10000
Source
圓方樹好毒瘤神奇啊qwq。
對於這題來說的話,首先把圓方樹弄出來
考慮詢問的兩個點,如果他們的lca是圓點,那麽直接樹上前綴和相加
如果不是圓點,那麽他們的lca一定是在一個環內,
我們可以維護出環內任意一點到環的根節點(也就是$dfn$最小的節點)的最短路徑
然後分類討論一下即可
對於一個點如何跳的它lca所在的環上,這裏有個騷操作。
我們考慮樹鏈剖分的過程
如果這個節點是重兒子,因為重兒子的dfs序是與父節點連續的,所以要求的點就是lca的dfs中對應的下一個節點
如果不是重兒子,那麽我們一直跳,直到跳到lca處位置,那麽要求的點就是來時離lca最近的那個節點
#include<cstdio> #include<vector> #include<cstring> using namespace std; const int MAXN = 1e6 + 10; inline int read() {char c = getchar(); int x = 0, f = 1; while(c < ‘0‘ || c > ‘9‘) {if(c == ‘-‘) f = -1; c = getchar();} while(c >= ‘0‘ && c <= ‘9‘) x = x * 10 + c - ‘0‘, c = getchar(); return x * f; } int N, M, Q; struct Edge { int u, v, w, nxt; }E[MAXN]; int head[MAXN], num = 1; inline void AddEdge(int x, int y, int z) { E[num] = (Edge){x, y, z, head[x]}; head[x] = num++; } vector<Edge> vec[MAXN]; inline void add_edge(int x, int y, int z) { vec[x].push_back((Edge){x, y, z}); vec[y].push_back((Edge){y, x, z}); } int low[MAXN], dfn[MAXN], times, fa[MAXN], cdis[MAXN], dis[MAXN], tot; inline void Build(int x, int y, int w) { for(int i = y; i != x; i = fa[i]) cdis[i] = w, w += dis[i]; cdis[++tot] = w; add_edge(tot, x, 0);//建方點 for(int i = y; i != x; i = fa[i]) add_edge(tot, i, min(cdis[i], w - cdis[i])); } void Tarjan(int x, int _fa) { dfn[x] = low[x] = ++times; fa[x] = _fa; for(int v, i = head[x]; i != -1; i = E[i].nxt) { if((v = E[i].v) == _fa) continue; if(!dfn[v]) dis[v] = E[i].w, Tarjan(v, x), low[x] = min(low[x], low[v]); else low[x] = min(low[x], dfn[v]); if(low[v] > dfn[x]) add_edge(x, v, E[i].w); } // can I take this into for ? for(int v, i = head[x]; i != -1; i = E[i].nxt) if(fa[(v = E[i].v)] != x && dfn[v] > dfn[x])//如果不是負邊,那麽一定出現了環 Build(x, v, E[i].w); } int siz[MAXN], son[MAXN], top[MAXN], deep[MAXN], Index = 0, point[MAXN]; void dfs1(int x, int _fa) { fa[x] = _fa; siz[x] = 1; for(int i = 0, v; i < vec[x].size(); i++) { if((v = vec[x][i].v) == _fa) continue; dis[v] = dis[x] + vec[x][i].w; deep[v] = deep[x] + 1; dfs1(v, x); siz[x] += siz[v]; if(siz[v] > siz[son[x]]) son[x] = v; } } void dfs2(int x, int topf) { top[x] = topf; point[dfn[x] = ++Index] = x; if(!son[x]) return ; dfs2(son[x], topf); for(int i = 0, v; i < vec[x].size(); i++) if(!top[v = vec[x][i].v]) dfs2(v, v); } int LCA(int x, int y) { while(top[x] != top[y]) { if(deep[top[x]] < deep[top[y]]) swap(x, y); x = fa[top[x]]; } if(deep[x] < deep[y]) swap(x, y); return y; } int Jump(int x, int lca) { int las; while(top[x] != top[lca]) las = top[x], x = fa[top[x]];//las = top[x] not x return x == lca ? las : point[dfn[lca] + 1];//這裏要寫lca } int abs(int x) { return x < 0 ? -x : x; } int main() { #ifdef WIN32 freopen("a.in", "r", stdin); freopen("a.out", "w", stdout); #endif tot = N = read(); M = read(); Q = read(); memset(head, -1, sizeof(head)); for(int i = 1; i <= M; i++) { int x = read(), y = read(), z = read(); AddEdge(x, y, z); AddEdge(y, x, z); } Tarjan(1, 0); dis[1] = 0; deep[1] = 1; //for(int i = 1; i <= N; i++) printf("%d ", dfn[i]); puts(""); //for(int i = 1; i <= N; i++) printf("%d ", vec[i].size()); puts(""); //printf("%d\n", tot); dfs1(1, 0); dfs2(1, 1); //for(int i = 1; i <= tot; i++) printf("%d ", top[i]); puts(""); while(Q--) { int x = read(), y = read(); int lca = LCA(x, y); if(lca <= N) {printf("%d\n", dis[x] + dis[y] - (dis[lca] << 1)); continue;} int p1 = Jump(x, lca); int p2 = Jump(y, lca); int ans = dis[x] - dis[p1] + dis[y] - dis[p2] + min(abs(cdis[p2] - cdis[p1]), cdis[lca] - abs(cdis[p2] - cdis[p1])); printf("%d\n", ans); } return 0; }
BZOJ2125: 最短路(圓方樹)