圓方樹:一種將由圖轉化而成的樹,從而大大了增長題目的可解性,且大多普遍用於仙人掌圖中。ios
針對仙人掌圖上的圓方樹:仙人掌是指一條邊至多隻被一個環包含的無向圖。算法
樹上的點:圓方樹上分爲兩類點,一類是圓點,一類是方點。圓點即原圖中全部的點,方點即爲了去環而新添加進去的,知足必定性質的點。spa
構造思路:圓圓邊直接加入,對於仙人掌中的任意一個環,每一個環上的點在圓方樹上對應的圓點向這個環對應的方點連邊,方點爲一個新建節點。code
環的根:指定一個圓點爲圓方樹的根,把方點的父親叫作這個方點對應的環的根。string
圓方邊邊權:若一個在環上的圓點不是環的根,它到對應方點的邊權爲到環的根的最短距離,環的根到環所對應的方點的邊權爲零。it
解題:io
多數是爲了能夠用樹上的算法,例如倍增、樹剖解決問題,以兩點間路徑的問題爲例:模板
若 \(lca\) 是圓點,那麼答案就是路徑上的貢獻;class
若 \(lca\) 是方點,則找到進入這個環的兩個點,這兩個點之間的有兩條路徑,選擇合題意的一條加入貢獻。stream
在樹鏈剖分中,進入一個環的兩個點有兩種狀況:一是一個爲 \(dfs\) 序比 \(lca\) 大 \(1\) 的點,即 \(lca\) 所在重鏈上的兒子,另外一個爲最後通過的 \(top\);二是最後通過的兩個 \(top\)。
洛谷模板題:
#include <cmath> #include <queue> #include <cstdio> #include <cctype> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxn = 40000 + 10; int n, m, q, head_g[maxn], head_t[maxn], st[17][maxn], edge_num_g, edge_num_t, dfn_num, col_num, top; int dfn[maxn], low[maxn], sta[maxn]; long long deep[maxn], dis[maxn], sum[maxn], sccsum[maxn]; struct Edge { int v, nxt; long long w; } edge_g[maxn << 2], edge_t[maxn << 2]; inline void Add_edge_g(int u, int v, long long w) { edge_g[++edge_num_g].v = v, edge_g[edge_num_g].w = w, edge_g[edge_num_g].nxt = head_g[u], head_g[u] = edge_num_g; } inline void Add_edge_t(int u, int v, long long w) { edge_t[++edge_num_t].v = v, edge_t[edge_num_t].w = w, edge_t[edge_num_t].nxt = head_t[u], head_t[u] = edge_num_t; } inline void Tarjan(int x, int p) { dfn[x] = low[x] = ++dfn_num, sta[++top] = x; for(int i = head_g[x]; i; i = edge_g[i].nxt) if( edge_g[i].v != p ) { if( dfn[edge_g[i].v] == 0 ) { sum[edge_g[i].v] = sum[x] + edge_g[i].w, Tarjan(edge_g[i].v, x), low[x] = min(low[x], low[edge_g[i].v]); if( low[edge_g[i].v] > dfn[x] ) Add_edge_t(x, edge_g[i].v, edge_g[i].w), Add_edge_t(edge_g[i].v, x, edge_g[i].w); // 樹邊,圓圓邊加入 } else if( dfn[edge_g[i].v] < low[x] ) { // 返祖邊,得環,創建方點及圓方邊 sccsum[++col_num] = sum[x] - sum[edge_g[i].v] + edge_g[i].w; // 得環長,col_num 認爲是方點編號 for(int j = top; sta[j] != edge_g[i].v; --j) { int _w = min(sum[sta[j]] - sum[edge_g[i].v], sccsum[col_num] - sum[sta[j]] + sum[edge_g[i].v]); // 到此環的根的距離,即圓方邊邊權 Add_edge_t(n + col_num, sta[j], _w), Add_edge_t(sta[j], n + col_num, _w); } Add_edge_t(n + col_num, edge_g[i].v, 0), Add_edge_t(edge_g[i].v, n + col_num, 0); // 環的根所對應的圓方邊權爲 0 low[x] = dfn[edge_g[i].v]; } } --top; } inline void Deep_fs(int x, int p) { for(int i = 1; i < 17; ++i) st[i][x] = st[i - 1][st[i - 1][x]]; for(int i = head_t[x]; i; i = edge_t[i].nxt) if( edge_t[i].v != p ) { st[0][edge_t[i].v] = x; dis[edge_t[i].v] = dis[x] + edge_t[i].w, deep[edge_t[i].v] = deep[x] + 1, Deep_fs(edge_t[i].v, x); } } inline long long Querry(int x, int y) { int lca = 0, res = dis[x] + dis[y]; if( deep[x] < deep[y] ) swap(x, y); for(int i = 16; i >= 0; --i) if( deep[st[i][x]] >= deep[y] ) x = st[i][x]; if( x == y ) lca = x; else { for(int i = 16; i >= 0; --i) if( st[i][x] != st[i][y] ) x = st[i][x], y = st[i][y]; lca = st[0][x]; } if( lca <= n ) return res - (dis[lca] << 1); if( dfn[x] > dfn[y] ) swap(x, y); return res - dis[x] - dis[y] + min(sum[y] - sum[x], sccsum[lca - n] - sum[y] + sum[x]); } int main(int argc, char const *argv[]) { scanf("%d%d%d", &n, &m, &q); for(int u, v, w, i = 1; i <= m; ++i) scanf("%d%d%d", &u, &v, &w), Add_edge_g(u, v, w), Add_edge_g(v, u, w); deep[1] = 1, Tarjan(1, 0), Deep_fs(1, 0); for(int u, v, i = 1; i <= q; ++i) scanf("%d%d", &u, &v), printf("%lld\n", Querry(u, v)); return 0; }