首页 > 其他分享 >圆方树

圆方树

时间:2024-07-17 15:12:15浏览次数:15  
标签:int tot fa 圆方树 low id e2

定义

圆方树:将无向图转化为树形结构的数据结构,使得树上 2 点路径上的点都是原图的必经点。

圆点:原无向图 \(G\) 中的点,仍然保留在圆方树中,称之为圆点。

方点:将每一个点双连通分量新建一个“方点”。

树边:每一个方点都向对应的点双内的圆点连边。

基本性质:

性质一:圆方树的总点数 = 原图点数 \(n\) + 点双个数 \(tot\),上限 \(2n - 1\)。

性质二:圆点是被方点隔开的,一条边的两个端点一定是圆点和方点。

性质三:圆点的度数就是包含该点的点双个数。

性质四:圆方树删除点 \(x\) 后剩余的结点的连通性与原图中删除 \(x\) 后的连通性等价。

性质五:原图中 \(x\) 到 \(y\) 的路径的必经点就是圆方树上 \(x\) 到 \(y\) 的圆点。

性质六:圆点为割点时才有超过 1 个儿子节点。

例题

CF487E Tourists

我们可以先建出园方树,然后在每个方结点维护其子结点的最小值,然后答案就是对应路径上点的最小值,这个可以用树链剖分 + 线段树实现。

时间复杂度: \(O(n\log n\log n)\)

代码有点长

#include <bits/stdc++.h>

using namespace std;

const int N = 200010, INF = 0x3f3f3f3f;

vector<int> e1[N], e2[N];

int dfn[N], low[N], _cnt, tot;
stack<int> stk;
multiset<int> s[N];

void tarjan(int u, int fa) {
    dfn[u] = low[u] = ++_cnt;
    stk.push(u);
    for (int to : e1[u]) {
        if (!dfn[to]) {
            tarjan(to, u);
            low[u] = min(low[u], low[to]);
            if (low[to] >= dfn[u]) {
                tot++;
                int p = 0;
                do {
                    p = stk.top();
                    stk.pop();
                    e2[tot].push_back(p);
                    e2[p].push_back(tot);
                } while (p != to);
                e2[tot].push_back(u);
                e2[u].push_back(tot);
            }
        }
        else if (to != fa) low[u] = min(low[u], dfn[to]);
    }
}

int n, m, q;
int sz[N], son[N], fa[N], dep[N];
int top[N], rk[N], id[N], cnt;

void dfs1(int u, int f) {
    sz[u] = 1; dep[u] = dep[f] + 1; fa[u] = f;
    for (int to : e2[u]) {
        if (to == f) continue;
        dfs1(to, u);
        sz[u] += sz[to];
        if (sz[son[u]] < sz[to]) son[u] = to;
    }
}

void dfs2(int u, int t) {
    top[u] = t; id[u] = ++cnt, rk[cnt] = u;
    if (son[u]) dfs2(son[u], t);
    for (int to : e2[u]) {
        if (to == fa[u] || to == son[u]) continue;
        dfs2(to, to);
    }
}

void dfs3(int u) {
    for (int to : e2[u]) {
        if (to == fa[u]) continue;
        if (u > n) s[u].insert(*s[to].begin());
        dfs3(to);
    }
}

struct node {
    int min;
} tr[N << 2];

node pushup(node l, node r) {
    return {min(l.min, r.min)};
}

void init(int u, int l, int r) {
    if (l == r) {
        tr[u].min = *s[rk[l]].begin();
        return;
    }
    int mid = l + r >> 1;
    init(u << 1, l, mid);
    init(u << 1 | 1, mid + 1, r);
    tr[u] = pushup(tr[u << 1], tr[u << 1 | 1]);
}

void update(int u, int l, int r, int x) {
    if (l == r) {
        tr[u].min = *s[rk[x]].begin();
        return;
    }
    int mid = l + r >> 1;
    if (x <= mid) update(u << 1, l, mid, x);
    else update(u << 1 | 1, mid + 1, r,  x);
    tr[u] = pushup(tr[u << 1], tr[u << 1 | 1]);
}

node query(int u, int l, int r, int pl, int pr) {
    if (pl <= l && r <= pr) return tr[u];
    int mid = l + r >> 1;
    if (pr <= mid) return query(u << 1, l, mid, pl, pr);
    else if (pl > mid) return query(u << 1 | 1, mid + 1, r, pl, pr);
    else return pushup(query(u << 1, l, mid, pl, pr),  query(u << 1 | 1, mid + 1, r, pl, pr));
}

node ask(int x, int y) {
    node ans = {INF};
    while (top[x] != top[y]) {
        if (dep[top[x]] < dep[top[y]]) swap(x, y);
        ans = pushup(ans, query(1, 1, tot, id[top[x]], id[x]));
        x = fa[top[x]];
    }
    if (dep[x] > dep[y]) swap(x, y);
    ans = pushup(ans, query(1, 1, tot, id[x], id[y]));
    if (x > n) ans = pushup(ans, query(1, 1, tot, id[fa[x]], id[fa[x]]));
    return ans;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> m >> q;
    for (int i = 1; i <= n; i++) {
        int x;
        cin >> x;
        s[i].insert(x);
    }
    for (int i = 1; i <= m; i++) {
        int u, v;
        cin >> u >> v;
        e1[u].push_back(v);
        e1[v].push_back(u);
    }

    tot = n;
    tarjan(1, 1);
    dfs1(1, 1);
    dfs2(1, 1);
    dfs3(1);
    init(1, 1, tot);

    char opt;
    int x, y;
    while (q--) {
        cin >> opt >> x >> y;
        if (opt == 'C') {
            int past = *s[x].begin();
            s[x].clear();
            s[x].insert(y);
            s[fa[x]].erase(past);
            s[fa[x]].insert(y);
            update(1, 1, tot, id[x]);
            update(1, 1, tot, id[fa[x]]);
        }
        else cout << ask(x, y).min << '\n';
    }
    return 0;
}

标签:int,tot,fa,圆方树,low,id,e2
From: https://www.cnblogs.com/Yuan-Jiawei/p/18303865

相关文章

  • 圆方树
    一些概念割点:无向图中,若删除点x及其连边,连通块变多,那么x为割点。点双连通:若点对x和y,删除任意非x和非y节点后,x和y仍然联通,称x和y点双连通。点双联通子图:无向图中的一个子图G,G中任意两点都是点双连通的,那么G为原图的一个点双连通子图。点双联通分量:无向图中的极大点双联通子图......
  • 【算法学习】圆方树——处理仙人掌的利器
    圆方树大概分两种,一个是圆方树,一个是广义圆方树。圆方树这可以解决仙人掌上的问题。任意一条边至多只出现在一条简单回路的无向连通图称为仙人掌。很多题解将其作为无向图构建,本文将其构建为外向树,在这个问题中两种构建方式不会影响求解。构建方式记读入的图为原图,构建的......
  • 圆方树
    圆方树这里的圆方树指广义圆方树。对于一张\(n\)个点的无向图,其中包含\(k\)个点双,那么这张图建出的圆方树一共有\(n+k\)个点,其中前\(n\)个点为原图中的点,称为圆点,后\(k\)个点每个点代表一个点双,称为方点,每个点双与其中包含的点连边构成一个菊花,这\(k\)个菊花经由图......
  • 圆方树学习笔记
    圆方树学习笔记圆方树是优秀的图论算法,从仙人掌图向无向图扩展,利用割点和点双联通分量的性质,实现了图向树的转换。对仙人掌的处理:圆方树——处理仙人掌的利器而且实现十分简单算法思路前置知识割点和桥,点双联通分量。思路对于一个无向图,圆方树理解可以如下:原图中点是圆......
  • 圆方树学习笔记
    今天在做ABC318G这道题,要用到圆方树的知识,于是就去学了圆方树。学习圆方树首先需要学习点双连通分量以及缩点,此处不多赘述。圆方树中分两种类型的点:圆点和方点。圆点指的是原来的无向图中的所有点,而方点指的是每一个点双连通分量所代表的点。相当于每一个点双连通分量就是一个......
  • 圆方树 useful things
    圆方树,是解决仙人掌问题的实用方法,假设最初图都是圆点,对于每个环新建一个方点并连接这个环上所有圆点,能很好规避同一个点可能属于很多个环的情况,并且发现build完之后是一棵树广义圆方树,能够不局限于去解决仙人掌问题,能上升到无向图层面,很好解决图上路径类,等等问题那么如何建立圆......
  • 圆方树 useful things
    圆方树,是解决仙人掌问题的实用方法,假设最初图都是圆点,对于每个环新建一个方点并连接这个环上所有圆点,能很好规避同一个点可能属于很多个环的情况,并且发现build完之后是一棵树广义圆方树,能够不局限于去解决仙人掌问题,能上升到无向图层面,很好解决图上路径类,等等问题那么如何建立圆......
  • 圆方树
    圆方树的引入我们知道,图没有很好的性质,而树有很多性质,并且容易通过很多方式来维护树上信息,因此将图上问题转化为树上问题是我们想要解决的。圆方树就是将图转化为树的数据结构。圆方树的分类圆方树分为两类:狭义圆方树,广义圆方树。狭义圆方树狭义圆方树是可以用来将仙人掌图转......
  • 圆方树
    为什么只写圆方树呢,因为点双代码比圆方树长一倍其实是因为边双可以被圆方树表示出来前言这里给tarjan中的low数组的定义明确一下,其代表的是包括自己在内的搜索子树内经过最多一条非树边能够到达的最浅节点边双这个很简单,如果有一个点的low值等于他的dfn序了,那它和栈......
  • 「学习笔记」圆方树
    圆方树最初是处理「仙人掌图」(每条边在不超过一个简单环中的无向图)的一种工具,不过发掘它的更多性质,有时我们可以在一般无向图上使用它。个人觉得,圆方树是一个很好的工具。圆方树的题目更多的侧重于想,而不是怎么建圆方树。前置知识——点双连通分量点双连通分量:不存在割点的图。......