【學習筆記】【Luogu P5236】【模板】靜態仙人掌
阿新 • • 發佈:2020-08-18
題目大意:
現在給出一個仙人掌圖(即每條邊最多隻出現在一個環裡),給出多個詢問,每個詢問求出兩點的最短距離。
正文:
概述:
仙人掌是圖,由於時空限制,直接求多源最短路徑會超時超空,所以我們通過 圓方樹 來將其轉化為樹上問題。
圓方樹:
關於圓方樹,要講得通俗易懂,原圖裡每個節點都是原點,將每個環里加入一個方點,方點直接連向環內各個節點,如圖:
這個建方點的操作用 Tarjan 做就行了!
inline void solve (int u, int v, int w) //建方點 { ++ext; int minn, pre = w, i = v; while (i != f[u][0]) { sum[i] = pre; pre += b[i]; i = f[i][0]; } sum[ext] = sum[u]; sum[u] = 0; i = v; while(i != f[u][0]) { minn = min(sum[i], sum[ext] - sum[i]); add_(ext, i, minn); add_(i, ext, minn); i = f[i][0]; } } void Tarjan(int u, int fa) { dfn[u] = low[u] = ++cnt; for (int i = head[u]; i; i = e[i].next) //Tarjan 模板 { int v = e[i].to, w = e[i].w; if(v == fa) continue; if(!dfn[v]) { f[v][0] = u; b[v] = w; dis[v] = dis[u] + w; Tarjan(v, u); low[u] = min(low[u], low[v]); } else low[u] = min(low[u], dfn[v]); if(low[v] <= dfn[u]) continue; // 建圓點 add_(u, v, w); add_(v, u, w); } for (int i = head[u]; i; i = e[i].next) //找到非樹邊(環),建方點 { int v = e[i].to; if(f[v][0] == u || dfn[v] <= dfn[u]) continue; solve(u, v, e[i].w); } }
對於剩下的問題——兩點距離,先找到 \(u,v\) 的最近公共祖先 \(a\),\(a\) 是圓點直接求。如果是方點:
假設 \(u,v\) 父親分別是 \(A,B\),發現如果是方點,答案就是 \(\operatorname{dis}(A,B)+\operatorname{dis}(u,A)+\operatorname{dis}(v,B)\)。
ll lca (int X, int Y) { int Lca, x = X, y = Y; if (d[x] > d[y]) { int t = x; x = y; y = t; } for (int i = 20; i >= 0; i--) if (d[f[y][i]] >= d[x]) y = f[y][i]; if (x == y) Lca = x; else { for (int i = 20; i >= 0; i--) if (f[x][i] != f[y][i]) { x = f[x][i]; y = f[y][i]; } Lca = f[y][0]; } ll calc = dis[X] + dis[Y] - (dis[Lca] << 1); if(Lca > n) { calc -= (dis[x] - dis[Lca]) + (dis[y] - dis[Lca]); calc += min(abs(sum[y] - sum[x]), sum[Lca] - abs(sum[y] - sum[x])); } return calc; }
全部程式碼:
初始圖和圓方樹記得分著存。
struct edge { int from, to, next, w; }e[M], ne[M]; int head[N], h[N], tot, total; void add(int u, int v, int w) { e[++tot] = (edge){u, v, head[u], w}, head[u] = tot; } void add_(int u, int v, int w) { ne[++total] = (edge){u, v, h[u], w}, h[u] = total; } int dfn[N], low[N], f[N][22], cnt, d[N], b[N]; //b[u]表示u到父節點的價值 ll dis[N], sum[N]; inline void solve (int u, int v, int w) //建方點 { ++ext; int minn, pre = w, i = v; while (i != f[u][0]) { sum[i] = pre; pre += b[i]; i = f[i][0]; } sum[ext] = sum[u]; sum[u] = 0; i = v; while(i != f[u][0]) { minn = min(sum[i], sum[ext] - sum[i]); add_(ext, i, minn); add_(i, ext, minn); i = f[i][0]; } } void Tarjan(int u, int fa) { dfn[u] = low[u] = ++cnt; for (int i = head[u]; i; i = e[i].next) //Tarjan 模板 { int v = e[i].to, w = e[i].w; if(v == fa) continue; if(!dfn[v]) { f[v][0] = u; b[v] = w; dis[v] = dis[u] + w; Tarjan(v, u); low[u] = min(low[u], low[v]); } else low[u] = min(low[u], dfn[v]); if(low[v] <= dfn[u]) continue; // 建圓點 add_(u, v, w); add_(v, u, w); } for (int i = head[u]; i; i = e[i].next) //找到非樹邊(環),建方點 { int v = e[i].to; if(f[v][0] == u || dfn[v] <= dfn[u]) continue; solve(u, v, e[i].w); } } queue<int> que; void dfs (int x, int fa) { d[x] = d[fa] + 1; f[x][0] = fa; for (int i = h[x]; i; i = ne[i].next) { int y = ne[i].to; if(y == fa) continue; dis[y] = dis[x] + ne[i].w; dfs (y, x); } } ll lca (int X, int Y) { int Lca, x = X, y = Y; if (d[x] > d[y]) { int t = x; x = y; y = t; } for (int i = 20; i >= 0; i--) if (d[f[y][i]] >= d[x]) y = f[y][i]; if (x == y) Lca = x; else { for (int i = 20; i >= 0; i--) if (f[x][i] != f[y][i]) { x = f[x][i]; y = f[y][i]; } Lca = f[y][0]; } ll calc = dis[X] + dis[Y] - (dis[Lca] << 1); if(Lca > n) { calc -= (dis[x] - dis[Lca]) + (dis[y] - dis[Lca]); calc += min(abs(sum[y] - sum[x]), sum[Lca] - abs(sum[y] - sum[x])); } return calc; } int main() { scanf ("%d%d", &n, &m); ext = n; 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); } f[1][0] = 0; Tarjan(1, 0); for (int i = 1; i <= ext; i++) d[i] = 0, dis[i] = 0; dfs(1, 0); for (int j = 1; j <= 20; j++) for (int i = 1; i <= ext; i++) f[i][j] = f[f[i][j - 1]][j - 1]; scanf ("%d", &q); for (int i = 1; i <= q; ++i) { int x, y; scanf ("%d%d", &x, &y); printf("%lld\n", lca(x, y)); } return 0; }