[学习笔记]圆方树广义圆方树
引入
偶爾,我們會遇到一些要在無向圖/仙人掌上做的問題,這些問題如果在樹上就會比較方便,那么我們就開始考慮能不能把原圖等效成一棵樹,然后就可以方便地亂搞了?
圓方樹就是一種將無向圖/仙人掌變成樹的數據結構
一般無向圖的圓方樹
構建
對于一般的無向圖,不滿足樹形結構的部分無非是邊雙聯通分量、點雙聯通分量
構建圓方樹時我們處理點雙聯通分量(一般無向圖中兩個點、一條邊的也算一個點雙)
具體做法是原圖中每個點是圓點
對于每個點雙,我們新建一個方點
點雙中原有的邊全部拆掉,而里面的圓點(原圖上的點)都向這個方點連邊
當然原圖中的點可能屬于多個點雙
舉個例子就是:
然后它就變成了一棵樹,然后什么樹鏈剖分、點分治、虛樹、樹形dp甚至LCT等各種(duliu)算法就可以在上面搞啦!唯一需要注意的是方點和圓點的維護方式可能不同
具體的構建過程寫法不唯一,可以一邊\(Tarjan\)一邊建樹,也可以存下屬于哪些點雙然后推倒重建
推倒重建版:
void Tarjan(int u, int fa) {dfn[u] = low[u] = ++idx;for (int i = G.head[u]; ~i; i = G.edge[i].next) {int v = G.edge[i].v;if (v == fa) continue;if (!dfn[v]) {stk[stop++] = v;Tarjan(v, u);low[u] = std::min(low[u], low[v]);if (low[v] >= dfn[u]) {int p; ++tot;do {p = stk[--stop];bel[p].push_back(tot);} while (p ^ v);bel[u].push_back(tot);}} else low[u] = std::min(low[u], dfn[v]);} } void rebuild() {G.init();for (int i = 1; i <= N; ++i) for (int j = 0; j < bel[i].size(); ++j)G.insert(i, bel[i][j]); }性質
這樣建出的圓方樹具有以下性質:
例題
還是要從例題入手,才能發現圓方樹性質的妙用,所以:
UOJ#30[Codeforces 487E]
JZOJ3225[BJOI2013]load
仙人掌的圓方樹
upd 2019.4.9 咕了好久,我終于來填坑了!!
好像上面那個叫廣義圓方樹,這個才是正統圓方樹來著,我又學錯順序了??
仙人掌
無向仙人掌是指一條邊至多在一個簡單環中的無向圖
構建
大體上類似于廣義圓方樹,但這里我們對一個環建方點,而不在同一個環上的兩個圓點直接連邊
就比如:
性質
可能存在圓方邊和圓圓邊,但沒有方方邊
然后容易發現原仙人掌的子仙人掌對應的圓方樹是整個圓方樹上的一個聯通塊
例題
洛谷模板題
首先建出圓方樹,然后考慮賦邊權
為了是原仙人掌上的最短路對應圓方樹上兩點間的路徑,我們按如下方式賦邊權:
然后仿照在樹上查詢兩點路徑一樣
但是這里需要分類討論:
如何找到2中的兩個點呢?
如果寫的是倍增,可以方便求出
如果寫的是樹鏈剖分,這兩個點有兩種情況:1.一個是dfs序比\(lca\)大1的點,一個是最后經過的\(top\);2.最后經過的兩個\(top\)
環上路徑可以用距離前綴和,再記錄一下每個環的邊權和求出
代碼(倍增)
#include <cstdio> #include <cstring> #include <iostream> #define MAXN 10005 #define MAXM 20005typedef long long LL; struct Graph {struct Edge {int v, next; LL w;Edge(int _v = 0, int _n = 0, LL _w = 0):v(_v), next(_n), w(_w) {}} edge[MAXM << 2];int head[MAXN << 1], cnt;void init() { memset(head, -1, sizeof head); cnt = 0; }void add_edge(int u, int v, int w) { edge[cnt] = Edge(v, head[u], w); head[u] = cnt++; }void insert(int u, int v, int w) { add_edge(u, v, w); add_edge(v, u, w); } };char gc(); LL read(); void Tarjan(int, int); LL query(int, int); void dfs(int);int N, M, Q; int dep[MAXN << 1], dist[MAXN << 1], anc[MAXN << 1][17];//圓方樹上的深度、到根的距離、祖先 LL sum[MAXN], sumd[MAXN];//sum:距離前綴和,也就是搜索樹上到根的距離。sumd:每個環的邊權和 int dfn[MAXN], low[MAXN], bcc_cnt, idx, stk[MAXN], top;;//Tarjan用到的 Graph G, T;//G:原圖。T:圓方樹 int main() {G.init(), T.init();N = read(), M = read(), Q = read();for (int i = 1; i <= M; ++i) {int u = read(), v = read(); LL w = read();G.insert(u, v, w);}Tarjan(1, 0);dfs(1);while (Q--) {int u = read(), v = read();printf("%lld\n", query(u, v));}return 0; } inline char gc() {static char buf[1000000], *p1, *p2;if (p1 == p2) p1 = (p2 = buf) + fread(buf, 1, 1000000, stdin);return p1 == p2 ? EOF : *p2++; } inline LL read() {LL res = 0, op; char ch = gc();while (ch != '-' && (ch < '0' || ch > '9')) ch = gc();op = (ch == '-' ? ch = gc(), -1 : 1);while (ch >= '0' && ch <= '9') res = (res << 1) + (res << 3) + ch - '0', ch = gc();return res * op; } void Tarjan(int u, int fa) {dfn[u] = low[u] = ++idx;stk[top++] = u;for (int i = G.head[u]; ~i; i = G.edge[i].next) {int v = G.edge[i].v; LL w = G.edge[i].w;if (v == fa) continue;if (!dfn[v]) {sum[v] = sum[u] + w;Tarjan(v, u);low[u] = std::min(low[u], low[v]);if (low[v] > dfn[u]) T.insert(u, v, w);//非樹邊只可能是返祖邊,可以這樣判 } else if (dfn[v] < low[u]) {//這里這樣判也是一樣的原因 low[u] = dfn[v];++bcc_cnt;sumd[bcc_cnt] = sum[u] + w - sum[v];for(int j = top - 1; stk[j] ^ v; --j) {int p = stk[j];T.insert(N + bcc_cnt, p, std::min(sum[p] - sum[v], sumd[bcc_cnt] - sum[p] + sum[v]));}T.insert(N + bcc_cnt, v, 0);}}--top; } void dfs(int u) {dep[u] = dep[anc[u][0]] + 1;for (int i = 1; i < 17 && anc[u][i - 1]; ++i) anc[u][i] = anc[anc[u][i - 1]][i - 1];for (int i = T.head[u]; ~i; i = T.edge[i].next) {int v = T.edge[i].v; LL w = T.edge[i].w;if (v == anc[u][0]) continue;dist[v] = dist[u] + w;anc[v][0] = u, dfs(v);} } LL query(int x, int y) {int lca; LL res = dist[x] + dist[y];if (dep[x] < dep[y]) std::swap(x, y);for (int i = 16; i >= 0; --i) if (dep[anc[x][i]] >= dep[y]) x = anc[x][i];if (x == y) lca = x;else {for (int i = 16; i >= 0; --i) if (anc[x][i] ^ anc[y][i]) x = anc[x][i], y = anc[y][i];lca = anc[x][0];}if (lca <= N) return res - (dist[lca] << 1);//lca是圓點 else {//lca是方點 if (dfn[x] > dfn[y]) std::swap(x, y);return res - dist[x] - dist[y] + std::min(sum[y] - sum[x], sumd[lca - N] - sum[y] + sum[x]);} } //Rhein_E轉載于:https://www.cnblogs.com/Rhein-E/p/10617334.html
總結
以上是生活随笔為你收集整理的[学习笔记]圆方树广义圆方树的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: vue组件间的传值方式及方法调用汇总
- 下一篇: 微信支付条码支付上线啦