📜  探戈树数据结构(1)

📅  最后修改于: 2023-12-03 14:54:42.833000             🧑  作者: Mango

探戈树数据结构

探戈树(Tango Tree)是一种支持动态换根和区间操作的数据结构。它是由大佬Y号在2019年提出的,它使用了分块技巧,将动态换根问题转化为了维护区间众数的问题,使用了线段树来维护众数信息,同时利用了众数出现位置的特殊性质,将链和树上的查询和修改操作都通过同一种方式实现,从而简化了程序的实现和减少了程序的复杂度。

适用场景

探戈树适用于树上的一些特殊操作,例如以下问题:

  • 树上路径查询或修改(不涉及环)
  • 树上重链剖分
  • 树上动态点分治
主要思想

探戈树的主要思想是将树上的动态换根问题转化为区间众数问题。在树上进行动态换根时,我们需要维护树的形态和结构,但是这样的话任何操作都会非常麻烦。探戈树的解决方案是维护节点到根的重链上与之相同的节点的数量,并通过线段树维护区间众数。这样的话,每当节点 x 换到了节点 y 的下面时,我们只需要修改以 y 为根的重链上 x 的数量,以及 y 到根节点路径上最靠近 y 的重儿子所在重链上相关节点的数量。同时,根据众数出现位置的特殊性质,所有的树上的修改和查询操作都可以通过重链上的区间众数来实现,从而简化了程序的实现和减少了程序的复杂度。

示例代码

探戈树的实现并不是非常复杂,但是相对其他数据结构来说比较难理解。以下代码片段演示了一个基本的探戈树的实现,仅供参考。

const int MAXN = 1e5;

struct TangoTree {
    int n;
    int cnt[MAXN], dep[MAXN], sz[MAXN], top[MAXN];
    int son[MAXN], fa[MAXN], id[MAXN], rk[MAXN], wt[MAXN];
    int vis[MAXN], a[MAXN];
    vector<int> G[MAXN];

    struct SegmentTree {
        int l, r, mx, cnt;
    } tr[MAXN << 2];

    void init(int _n) {
        n = _n;
        memset(cnt, 0, sizeof(cnt));
        memset(dep, 0, sizeof(dep));
        memset(sz, 0, sizeof(sz));
        memset(son, 0, sizeof(son));
        memset(fa, 0, sizeof(fa));
        memset(id, 0, sizeof(id));
        memset(rk, 0, sizeof(rk));
        memset(wt, 0, sizeof(wt));
        memset(vis, 0, sizeof(vis));
        memset(a, 0, sizeof(a));
        for (int i = 1; i <= n; ++i) {
            G[i].clear();
        }
    }

    void addEdge(int u, int v) {
        G[u].push_back(v);
        G[v].push_back(u);
    }

    void dfs1(int u, int f, int d) {
        dep[u] = d;
        fa[u] = f;
        sz[u] = 1;
        for (auto v : G[u]) {
            if (v == f) continue;
            dfs1(v, u, d + 1);
            sz[u] += sz[v];
            if (sz[v] > sz[son[u]]) {
                son[u] = v;
            }
        }
    }

    void dfs2(int u, int tp) {
        top[u] = tp;
        wt[id[u] = ++cnt[tp]] = u;
        rk[u] = cnt[tp];
        if (son[u]) {
            dfs2(son[u], tp);
            for (auto v : G[u]) {
                if (v != fa[u] && v != son[u]) {
                    dfs2(v, v);
                }
            }
        }
    }

    void pushUp(int u) {
        int ls = u << 1, rs = u << 1 | 1;
        if (tr[ls].mx > tr[rs].mx) {
            tr[u].mx = tr[ls].mx;
            tr[u].cnt = tr[ls].cnt;
        } else if (tr[ls].mx < tr[rs].mx) {
            tr[u].mx = tr[rs].mx;
            tr[u].cnt = tr[rs].cnt;
        } else {
            tr[u].mx = tr[ls].mx;
            tr[u].cnt = tr[ls].cnt + tr[rs].cnt;
        }
    }

    void build(int u, int l, int r) {
        tr[u].l = l, tr[u].r = r;
        if (l == r) {
            tr[u].mx = a[wt[l]];
            tr[u].cnt = 1;
            return;
        }
        int mid = (l + r) >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        pushUp(u);
    }

    void update(int u, int x, int val) {
        if (tr[u].l == tr[u].r) {
            tr[u].mx = val;
            tr[u].cnt = 1;
            return;
        }
        int mid = (tr[u].l + tr[u].r) >> 1;
        if (x <= mid) {
            update(u << 1, x, val);
        } else {
            update(u << 1 | 1, x, val);
        }
        pushUp(u);
    }

    int queryMax(int u, int l, int r) {
        if (tr[u].l >= l && tr[u].r <= r) {
            return tr[u].mx;
        }
        int res = -1, mid = (tr[u].l + tr[u].r) >> 1;
        if (l <= mid) {
            res = max(res, queryMax(u << 1, l, r));
        }
        if (r > mid) {
            res = max(res, queryMax(u << 1 | 1, l, r));
        }
        return res;
    }

    int queryCnt(int u, int l, int r) {
        if (tr[u].l >= l && tr[u].r <= r) {
            return tr[u].cnt;
        }
        int res = 0, mid = (tr[u].l + tr[u].r) >> 1;
        if (l <= mid) {
            res += queryCnt(u << 1, l, r);
        }
        if (r > mid) {
            res += queryCnt(u << 1 | 1, l, r);
        }
        return res;
    }

    void initTangoTree(int root) {
        dfs1(root, 0, 1);
        dfs2(root, root);
        build(1, 1, n);
    }

    int getLca(int u, int v) {
        while (top[u] != top[v]) {
            if (dep[top[u]] < dep[top[v]]) swap(u, v);
            u = fa[top[u]];
        }
        return dep[u] < dep[v] ? u : v;
    }

    int queryPathMax(int u, int v) {
        int ans = -1;
        while (top[u] != top[v]) {
            if (dep[top[u]] < dep[top[v]]) swap(u, v);
            ans = max(ans, queryMax(1, id[top[u]], id[u]));
            u = fa[top[u]];
        }
        if (dep[u] < dep[v]) swap(u, v);
        ans = max(ans, queryMax(1, id[v], id[u]));
        return ans;
    }

    int queryPathCnt(int u, int v, int k) {
        int ans = 0;
        while (top[u] != top[v]) {
            if (dep[top[u]] < dep[top[v]]) swap(u, v);
            ans += queryCnt(1, id[top[u]], id[u]);
            u = fa[top[u]];
        }
        if (dep[u] < dep[v]) swap(u, v);
        ans += queryCnt(1, id[v], id[u]);
        if (queryPathMax(u, v) == k) {
            ans -= queryCnt(1, id[u], id[getLca(u, v)]);
            ans += queryCnt(1, id[v], id[getLca(u, v)]);
        }
        return ans;
    }

    void updateNode(int u, int k) {
        update(1, id[u], k);
    }

    void updatePath(int u, int v, int k) {
        while (top[u] != top[v]) {
            if (dep[top[u]] < dep[top[v]]) swap(u, v);
            update(1, id[top[u]], k);
            u = fa[top[u]];
        }
        if (dep[u] < dep[v]) swap(u, v);
        update(1, id[v], k);
    }
};
参考资料
  1. 探戈树 - CNBlogs
  2. Tango 树教程 - Ynoi
  3. LGOJ4873 - 题解 - Luogu
  4. 探戈树的思想与实现 - Ak IOI Hub
  5. 探戈树的可视化教程(不想看代码直接点进来) - YouTube