[學習筆記]圓方樹&廣義圓方樹

引入

偶爾,咱們會遇到一些要在無向圖/仙人掌上作的問題,這些問題若是在樹上就會比較方便,那麼咱們就開始考慮能不能把原圖等效成一棵樹,而後就能夠方便地亂搞了?html

圓方樹就是一種將無向圖/仙人掌變成樹的數據結構ios

通常無向圖的圓方樹

構建

對於通常的無向圖,不知足樹形結構的部分無非是邊雙聯通份量、點雙聯通份量算法

構建圓方樹時咱們處理點雙聯通份量(通常無向圖中兩個點、一條邊的也算一個點雙)數據結構

具體作法是原圖中每一個點是圓點ui

對於每一個點雙,咱們新建一個方點spa

點雙中原有的邊所有拆掉,而裏面的圓點(原圖上的點)都向這個方點連邊3d

固然原圖中的點可能屬於多個點雙code

舉個例子就是:htm

而後它就變成了一棵樹,而後什麼樹鏈剖分、點分治、虛樹、樹形dp甚至LCT等各類(duliu)算法就能夠在上面搞啦!惟一須要注意的是方點和圓點的維護方式可能不一樣blog

具體的構建過程寫法不惟一,能夠一邊\(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]);
}

性質

這樣建出的圓方樹具備如下性質:

  1. 顯然建出的是一堆無根樹構成的森林,原圖上聯通的點圓方樹(森林)上也聯通
  2. 相鄰的點形狀一定不一樣
  3. 全部度數\(\ge 1\)的圓點在原圖中都是割點

例題

仍是要從例題入手,才能發現圓方樹性質的妙用,因此:

UOJ#30[Codeforces 487E]

JZOJ3225[BJOI2013]load

仙人掌的圓方樹

upd 2019.4.9 咕了很久,我終於來填坑了!!

好像上面那個叫廣義圓方樹,這個纔是正統圓方樹來着,我又學錯順序了??

仙人掌

無向仙人掌是指一條邊至多在一個簡單環中的無向圖

構建

大致上相似於廣義圓方樹,但這裏咱們對一個環建方點,而不在同一個環上的兩個圓點直接連邊

就好比:

性質

可能存在圓方邊和圓圓邊,但沒有方方邊

而後容易發現原仙人掌的子仙人掌對應的圓方樹是整個圓方樹上的一個聯通塊

例題

洛谷模板題

首先建出圓方樹,而後考慮賦邊權

爲了是原仙人掌上的最短路對應圓方樹上兩點間的路徑,咱們按以下方式賦邊權:

  1. 指定一個圓點爲圓方樹的根,而後咱們把一個方點的父親叫作這個方點對應的環的根
  2. 若一個在環上的圓點不是環的根,它到對應方點的邊權爲到環的根的最短距離
  3. 環的根到環所對應的方點的邊權爲0

而後仿照在樹上查詢兩點路徑同樣

可是這裏須要分類討論:

  1. \(lca\)是圓點,那麼答案就是路徑長度
  2. \(lca\)是方點,則找到進入這個環的兩個點,這兩個點之間的有兩條路徑,比較一下選較短的

如何找到2中的兩個點呢?

若是寫的是倍增,能夠方便求出

若是寫的是樹鏈剖分,這兩個點有兩種狀況:1.一個是dfs序比\(lca\)大1的點,一個是最後通過的\(top\);2.最後通過的兩個\(top\)

環上路徑能夠用距離前綴和,再記錄一下每一個環的邊權和求出

代碼(倍增)

#include <cstdio>
#include <cstring>
#include <iostream>
#define MAXN 10005
#define MAXM 20005

typedef 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
相關文章
相關標籤/搜索