📅  最后修改于: 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);
}
};