欢迎光临 ~|

Laijinyi

园龄:1年7个月粉丝:2关注:2

📂总结
🔖算法
2024-08-27 10:27阅读: 203评论: 0推荐: 0

简单萌萌哒 Top Tree(上)

前情提要(?

Top Cluster 分解与 Top Tree

情景导入

我们总是想要以一种合适的方式对树进行划分,但是对于菊花图而言,基于点的划分总是不合适的,这启发我们基于边进行划分。事实上可以证明,基于边的划分总是可行的。

Top Cluster 分解就是一种基于边的划分方式,下面我们来介绍他。

簇、簇操作、树的簇表示法

定义:

  • 一个簇可以表示为一个三元组 (u,v,E),其中 u,v 为树的节点,称为簇的界点(或端点),E 为一个边集,表示该簇包含的边,路径 (u,v) 称为簇路径
  • 对于树的一条边 (u,v)(u,v,{(u,v)}) 是一个簇。
  • 对于簇 a=(u,v,Ea),簇 b=(u,w,Eb),EaEb=,记 rake(a,b)=(u,v,EaEb)
  • 对于簇 a=(u,v,Ea),簇 b=(v,w,Eb),EaEb=,记 compress(a,b)=(u,w,EaEb)

其中 Rake 和 Compress 是树收缩的两种基本操作。形象的理解,一个簇可以看成一条边,Rake 就是把一条边“拍”到另一条边上,Compress 就是把两条边“拼”成一条;Rake 是去掉一度点,Compress 是去掉二度点。如下图:

image

显然,一棵树可以不断地进行这两种操作来合并为一个簇。一种方法是,不断地 Rake 来剥掉叶子。

进行若干次收缩后,用簇路径来代替原来的边,可以得到一个树形结构(下图左),称为收缩树;还可以得到一棵二叉树(下图右),每个点代表一个簇,其两个儿子表示 Compress 或 Rake 操作的两个簇。我们把这棵二叉树称为 Top Tree。每个叶子节点是一条边,称为基簇;其根节点表示整棵树的簇,称为根簇

image

(右图:圆点:Rake 而来的点,称为 Rake 节点;方点:Compress 而来的点,称为 Compress 节点;点上的两个小写字母表示这个簇的界点)

基于重量平衡的静态 Top Tree

为了方便问题的解决,我们希望 Top Tree 的深度是 O(logn) 的。下面介绍一种构建方法:

首先注意到,簇的合并方式 与 轻重链剖分 有某种共性:Compress 相当于重链上 Pushup,Rake 相当于轻子树信息的合并。所以我们对原树进行轻重链剖分,DFS 地自底向上处理每条重链,并认为每个轻子树已经合并为一个簇。

对于重链上的每个点 u,把 fa(u) 的轻子树的簇 Rake 到边 (u,fa(u)),这里需要分治地 Rake 保证深度不会太大,称这样操作形成的树为 Rake 树。

然后我们得到了排成一条链的簇,同样分治地 Compress 这些簇来保证深度,称这样操作形成的树为 Compress 树。

显然,这样合并得到的 Top Tree 的深度为 O(log2n)。如何优化?

直接借用“全局平衡二叉树”的思想,在分治 Compress 或 Rake 时按簇的大小选择带权中点作为分治中点,这样向上跳两次子树大小至少翻倍,于是 Top Tree 的深度就为 O(logn) 了。

按这样构建出的 Top Tree 拥有一些很好的性质:

  • 所有簇的界点都有祖先关系。
  • 簇的上界点为整个簇中深度最小的点。
  • Compress 树的中序遍历是按照深度升序遍历,从上到下。

Top Tree 是关于边的划分,而我们时常需要维护点的信息,为此我们定义:一个簇包含其连通块中的所有除上界点以外的点,这样每个点在每一层中最多属于一个簇。根节点可以加入一条额外边或特殊考虑来解决。

簇与 Top Tree 的性质

我们约定,如果没有特殊说明,“簇”都指的是 Top Tree 上的簇。下面列出一些比较显然但是重要的性质:

  • 根据定义,簇中只有不超过 2 个点与外部点邻接,这两个点一定是界点。
  • 任意两个簇,要么相互包含,要么两者的交最多只有一个点,且这个点是两个簇的共有的界点。
  • 一个不是基簇的簇,其两个子簇恰好有一个共有的界点,我们称其为该簇的中心点。
  • 从外部经过一个簇的路径必定经过该簇的界点。特别地,如果这个路径完全跨过了该簇,那么该簇的簇路径是其的一个子路径。

Top Tree 是一棵二叉树,每个点代表一个簇,父亲由儿子合并而来,叶子是一条条边的基簇。

Top Tree 的结构在某种意义上类似于线段树:

  • 线段树的每个节点是一个区间,最多只有两端与外部邻接,而 Top Tree 的每个节点是一个簇,最多只有两端与外部邻接;(这也是他的信息可快速合并的原因之一)
  • 他们都维护子树信息并,是一种二叉分治结构,支持分治、二分,是 Leafy 的;
  • 他们的树高都应该是 O(logn)
  • ……

所以我们可以将 Top Tree 直观理解为树上的线段树。

Top Tree 的实现与维护

在讲 Top Tree 的实际应用前,我们先讲一下如何实现和维护 Top Tree。

一些可以简单地快速合并的树上信息的例子
  • 簇内点数 s

    scompress(x,y)=sx+sysrake(x,y)=sx+sy

  • 簇路径长度 d

    dcompress(x,y)=dx+dydrake(x,y)=dx

  • 上/下端点到簇内最远点距离 t0/1:(以上端点为例)

    tcompress(x,y)=max(tx,dx+ty)trake(x,y)=max(tx,ty)

  • 簇内直径大小 a

    acompress(x,y)=max(ax,ay,tx,1+ty,0)arake(x,y)=max(ax,ay,tx,0+ty,0)

静态 Top Tree 的实现

首先定义 Cluster 表示一个簇的信息,其中 u 是上界点,v 是下界点。然后有 RakeCompress 两个基本操作。

struct Cluster {
int u, v;
int dis; // 簇路径长度
int siz; // 簇内点数
};
Cluster Rake(const Cluster &x, const Cluster &y) {
return { x.u, x.v, x.dis, x.siz + y.siz };
}
Cluster Compress(const Cluster &x, const Cluster &y) {
return { x.u, y.v, x.dis + y.dis, x.siz + y.siz };
}

然后定义 Node 类,表示 Top Tree 上的点,并实现一个对 Cluster 序列带权分治建树的函数 DAC

struct Node {
int ch[2], fa, type; // type 0/1/2 分别为 base/rake/compress
Cluster x;
int &operator[](int x) { return ch[x]; }
} f[1000005];
int cnt;
void Pushup(int x) {
if (f[x].type == 0) return;
f[x].x = (f[x].type == 1 ? Rake : Compress)(f[f[x][0]].x, f[f[x][1]].x);
}
int Merge(int u, int v, int type) {
f[++cnt] = { { u, v }, 0, type };
f[u].fa = f[v].fa = cnt;
Pushup(cnt);
return cnt;
}
int DAC(vector<int> &vec, int l, int r, int type) {
if (l == r) return vec[l];
int sum = 0;
for (int i = l; i <= r; ++i) sum += f[vec[i]].x.siz;
int mid = r - 1;
for (int i = l, cur = 0; i <= r; ++i) {
cur += f[vec[i]].x.siz;
if (cur * 2 >= sum) {
mid = i;
break;
}
}
return Merge(DAC(vec, l, mid, type), DAC(vec, mid + 1, r, type), type);
}

最后是整棵树的建树过程:

int idf[100005]; // (u, fa[u]) 这个基簇的编号
int siz[100005], fa[100005], son[100005];
vector<int> G[100005];
void DFS1(int u, int fth = 0) {
siz[u] = 1, fa[u] = fth;
for (int v : G[u]) {
if (v == fth) continue;
f[++cnt] = { { 0, 0 }, 0, 0, { u, v, 1, 1 } };
DFS1(v, u), siz[u] += siz[v];
if (siz[v] > siz[son[u]]) son[u] = v;
}
}
int Build(int top) {
vector<int> cpr;
if (idf[top]) cpr.push_back(idf[top]);
for (int u = top; son[u]; u = son[u]) {
vector<int> rke;
// 先把轻子树全部合并起来
for (int v : G[u]) if (v != son[u] && v != fa[u]) rke.push_back(Build(v));
if (!rke.empty()) {
// 分治 rake 其他子树,最后 rake 到重儿子上
cpr.push_back(Merge(idf[son[u]], DAC(rke, 0, (int)rke.size() - 1, 1), 1));
} else {
cpr.push_back(idf[son[u]]);
}
}
return DAC(cpr, 0, (int)cpr.size() - 1, 2);
}

这样,我们就初步实现了一棵静态 Top Tree。

动态 Top Tree 的维护

动态 Top Tree,就是支持一些动态操作的 Top Tree:

  • expose(u,v):将根簇变为簇路径为 (u,v) 的一个簇。
  • link(u,v):在 u,v 间连边。
  • cut(u,v):断开 u,v 之间的边。

有一些数据结构可以实现动态 Top Tree,其中最好写实用的就是 SATT(Self-Adjusting Top Tree),接下来我们来介绍他。

注:这里的 SATT 不同于原论文版本,但是思想是相同的。

SATT 的组合结构

首先,静态 Top Tree 本质上是在描述一个链分治的过程,也就是每次选择一条重链,把剩下的部分递归分解,然后分治把轻儿子合并到重边上(对应 Rake Tree),再分治合并重链。(对应 Compress Tree)

接下来,我们把原本静态 Top Tree 的结构用一棵三叉树的结构替代:

  • 对于每个 Compress 节点,其左右儿子不变仍然描述重链的分治树,而中儿子挂上一棵 Rake Tree,表示先把这棵 Rake Tree 合并(Rake)到这个 Compress 节点上。
  • 对于每个 Rake 节点,它的左右儿子仍然描述分治 Rake 的过程,而中儿子挂上一棵 Compress Tree,表示把左右儿子 Rake 到中儿子上。

这就得到了 SATT 的实际结构。

注意,这里的分治过程是非 Leafy 的,也就是 Compress 节点本身就代表一个点,Rake 节点本身代表它的中儿子。

整理一下:

  • SATT 是一棵三叉树,每个节点代表一个簇。
  • 有两种节点:Compress 节点或 Rake 节点
  • 对于 Compress 节点:
    • 它的左右儿子是 Compress 节点,中儿子是 Rake 节点;
    • 表示它所代表的簇,由它自身所代表的节点与中儿子 Rake,再与左右儿子 Compress 得到;
    • 它与它的左右儿子描述了一条链的分治树。
  • 对于 Rake 节点:
    • 它的左右儿子是 Rake 节点,中儿子是 Compress 节点;
    • 表示它所代表的簇,由中儿子分别与左右儿子 Rake 得到;
    • 它与它的儿子描述了分治 Rake 的过程。
    • 一个 Rake 节点一定有一个中儿子。

这一结构看起来很像平衡树维护轻儿子的 LCT,或者非 Leafy 的 AAAT(前情提要 中有):一条实链被一棵 Compress Tree 维护,然后轻儿子被 Rake Tree 挂在下面。

附一张图:

image

一个大家经常搞不清楚的地方是,点是如何在 SATT 中维护的。

我们对簇的定义稍加修改,即可严格支持点的维护:

  • 一个簇可以表示为一个四元组 (u,v,E,V),其中 u,v 为簇的界点(或端点),E 为该簇包含的边集,V 为该簇包含的点集。
  • 对于树的一条边 (u,v)(u,v,{(u,v)},) 是一个簇。
  • 对于树的一个点 u(u,u,,{u}) 是一个簇。
  • 对于簇 a=(u,v,Ea,Va),簇 b=(u,w,Eb,Vb),EaEb=VaVb=,记 rake(a,b)=(u,v,EaEb,VaVb)
  • 对于簇 a=(u,v,Ea,Va),簇 b=(v,w,Eb,Vb),EaEb=VaVb=,记 compress(a,b)=(u,w,EaEb,VaVb)

这样,我们只需把一个单点所组成的簇,作为 Compress 节点加入 SATT,点的信息放在它对应的 Compress 节点上加入即可。

注意到无论一个簇的界点是哪个点,是否被包含在这个簇中,都不影响我们维护信息。

下图不包含所有情况,只是举例而已。

这样定义的簇应该形如:

image

SATT 中一次合并得到 Compress / Rake 节点,在原树中应该像这样:

image

(图中方点表示 Compress 节点,圆点表示 Rake 节点,下同)

SATT 的动态操作

注意到重链剖分、LCT 之间的关系,与静态 Top Tree、SATT 之间的关系比较类似:

  • LCT 本质上是把重链剖分的线段树变为可旋转、可加入删除一条实链的 Splay(虚实转化),实现了动态化。
  • 那么 Top Tree 是否可以支持旋转、插入删除一个簇呢?

Rotate & Splay

注意到,在保持中儿子不变的情况下,不管是 Rake Tree 还是 Compress Tree 我们都只关心它的左右儿子的中序遍历,而非树的具体形态,这意味着它是支持旋转的:

image

那么就自然可以 Splay 了,这个东西与 LCT 的是相同的。

Splice

在讨论 Access 操作之前,我们先介绍一个操作 splice(u),表示将簇 u(一定是 Rake 节点)的中儿子插入到它父亲(一定是 Compress 节点)的簇上,如下图:

image

先把 u 旋到它所在 Rake Tree 的根,设其父亲为 v,其中儿子为 x,容易知道 xv 一定是 Compress 节点。然后把 v 旋到这棵 Compress Tree 的根。

我们希望 x 成为 v 的右儿子。那么讨论:

  • v 有右儿子,直接让 xv 的右儿子交换即可。
  • 否则,我们让 x 挂到 v 的右儿子上,此时 u 失去了中儿子,作为 Rake 节点已经不合法,那么我们将 u 的左右子树做 Splay 合并后删去 u 即可。

最后还要对必要的点 Pushup。

image

Access

access(x) 表示让点 x 与原树的根节点组成根簇的界点。

有了 splice 操作后其实就简单了:

首先 splay(x)。我们希望 x 成为当前簇的一个界点,即此时在 Compress Tree 中 x 没有右儿子。

  • x 有右儿子,直接新建点 y 然后把 x 的中儿子、右儿子分别挂到 y 的左儿子、中儿子上,再令 y 成为 x 的中儿子即可。
  • 然后一直 splice(fa(x))xfa(x) 直到 x 成为根。

最后 Splay 一下原始的 x

第一步称为 Local Splay,如下图:

image

第二步称为 Global Splay,如图:

image

这样一直重复。

Makeroot

显然,先 access(x) 再翻转 x 子树即可。翻转标记只在 Compress 节点之间下传即可。

Expose

expose(u,v) 将根簇的簇路径变为 (u,v)

显然,先 makeroot(u)access(v) 即可。

Link

link(x,y,z) 表示连边 x,y,这条边在 SATT 上编号为 z

access(x),再 makeroot(y),再把 y 挂到 x 的右儿子、z 挂到 y 的左儿子即可。

image

Cut

expose(x,y),再删除 x 的右儿子,最后断开 x,y 即可。

如图:

image

以上就是 SATT 的基本操作。

时间复杂度证明:不会。

SATT 维护其他信息主要是通过修改 Pushup 函数,或非局部搜索来求。

SATT 的代码实现

Sone1 为例,我们来介绍如何实现 SATT。

这题要求我们维护:

  • 换根,换父亲
  • 子树 / 链的点权 min,max,sum
  • 子树 / 链 加 / 赋值

对于链,直接 expose(u,v) 就是所求;对于子树,考虑 expose(rt,u) 然后 u 以及其中子树就是所求。

所以可以维护这些信息:

  • 簇路径的 kx+b 标记
  • 簇内簇路径以外部分的 kx+b 标记
  • 簇路径的 (siz,min,max,sum) 信息
  • 簇内簇路径以外部分的 (siz,min,max,sum) 信息

而原本 SATT 要求我们维护:

  • 该点权值
  • 这个点的三个儿子和父亲
  • 子树翻转标记

我个人还习惯维护 lef,rig 表示这个子树内的最左 / 最右点。

那么我们首先实现 TagDat 类:

const int inf = 1e9;
struct Tag {
int k, b;
Tag(int k = 1, int b = 0) : k(k), b(b) {}
bool NE() { return k != 1 || b != 0; }
};
struct Dat {
int siz, min, max, sum;
Dat(int siz = 0, int min = inf, int max = -inf, int sum = 0) : siz(siz), min(min), max(max), sum(sum) {}
};
Tag operator+ (const Tag &x, const Tag &y) {
return { x.k * y.k, x.b * y.k + y.b };
}
int operator+ (const int &x, const Tag &y) {
return x * y.k + y.b;
}
Dat operator+ (const Dat &x, const Tag &y) {
return {
x.siz,
x.min == inf ? inf : x.min + y,
x.max == -inf ? -inf : x.max + y,
x.sum * y.k + x.siz * y.b
};
}
Dat operator+ (const Dat &x, const Dat &y) {
return { x.siz + y.siz, min(x.min, y.min), max(x.max, y.max), x.sum + y.sum };
}

然后定义 Node 类,以及一些基本函数。

struct Node {
int fa, ch[3]; // 0/1/2 表示 左/右/中 儿子
int lef, rig, val; // 子树内最左点、最右点,该点权值
Dat pat, sub; // 簇路径信息,簇路径以外部分信息
Tag tpa, tsu; // 簇路径标记,簇路径以外部分标记
bool rev; // 子树翻转标记
void Clear() {
fa = ch[0] = ch[1] = ch[2] = lef = rig = val = 0;
rev = false, pat = sub = Dat(), tpa = tsu = Tag();
}
} f[1000005];
int cnt, tp, st[1000005]; // 点数,内存回收
int &ls(int x) { return f[x].ch[0]; }
int &ms(int x) { return f[x].ch[2]; }
int &rs(int x) { return f[x].ch[1]; }
int &fa(int x) { return f[x].fa; }
int &lef(int x) { return f[x].lef; }
int &rig(int x) { return f[x].rig; }
int NewNode() { return tp ? st[tp--] : ++cnt; }

然后是一些基本的操作函数:

// 找出 x 是它父亲的哪个儿子
int Get(int x) { return ls(fa(x)) == x ? 0 : (rs(fa(x)) == x ? 1 : 2); }
// 判断 x 不在当前 Compress Tree / Rake Tree 的根
bool Nrt(int x) { return ls(fa(x)) == x || rs(fa(x)) == x; }
// 把 x 挂到 fx 的 t 儿子上
void Set(int x, int fx, int t) { if (x) f[fa(x)].ch[Get(x)] = 0, fa(x) = fx; f[fx].ch[t] = x; }
// 删除点 x
void Clear(int x) { f[st[++tp] = x].Clear(); }
// 对 x 施加翻转标记
void Rev(int x) { swap(ls(x), rs(x)), swap(lef(x), rig(x)), f[x].rev ^= 1; }
// 对 x 施加簇路径标记
void PathTag(int x, Tag w) {
f[x].val = f[x].val + w;
f[x].pat = f[x].pat + w;
f[x].tpa = f[x].tpa + w;
}
// 对 x 施加簇路径以外部分标记
void SubTag(int x, Tag w) {
f[x].sub = f[x].sub + w;
f[x].tsu = f[x].tsu + w;
}

然后是 PushdownPushup 函数

void Down(int x, int t) {
// t = 0 表示 x 是 Compress 节点,t = 1 表示 x 是 Rake 节点,下同
if (!t) {
if (f[x].rev) {
// 翻转标记只在 Compress 节点之间下传
Rev(ls(x));
Rev(rs(x));
f[x].rev = false;
}
if (f[x].tpa.NE()) {
// 簇路径标记也只在当前 Compress Tree 中下传
PathTag(ls(x), f[x].tpa);
PathTag(rs(x), f[x].tpa);
f[x].tpa = Tag();
}
if (f[x].tsu.NE()) {
// 簇路径以外部分的标记,下传给所有儿子
SubTag(ls(x), f[x].tsu);
SubTag(rs(x), f[x].tsu);
SubTag(ms(x), f[x].tsu);
f[x].tsu = Tag();
}
} else {
if (f[x].tsu.NE()) {
// 对于 Rake 节点,直接下传即可
SubTag(ls(x), f[x].tsu);
SubTag(rs(x), f[x].tsu);
// 对于 Compress 节点,因为是修改整个簇,所以下传给两种标记
SubTag(ms(x), f[x].tsu);
PathTag(ms(x), f[x].tsu);
f[x].tsu = Tag();
}
}
}
void Pushdown(int x, int t) { if (Nrt(x)) Pushdown(fa(x), t); Down(x, t); }
void Pushup(int x, int t) {
// 更新最左最右点
lef(x) = ls(x) ? lef(ls(x)) : x;
rig(x) = rs(x) ? rig(rs(x)) : x;
if (!t) {
// 更新簇路径信息,也是 Compress Tree 的子树信息
f[x].pat = f[ls(x)].pat + Dat(1, f[x].val, f[x].val, f[x].val) + f[rs(x)].pat;
// 更新非簇路径部分信息
f[x].sub = f[ls(x)].sub + f[ms(x)].sub + f[rs(x)].sub;
} else {
// 对于 Rake 节点,我们令它的 sub 维护整个簇的信息
f[x].sub = f[ls(x)].sub + f[rs(x)].sub + f[ms(x)].sub + f[ms(x)].pat;
}
}

然后实现 RotateSplay 函数:

void Rot(int x, int t) {
int y = fa(x), z = fa(y), d = Get(x), p = f[x].ch[!d];
if (z) f[z].ch[Get(y)] = x;
if (p) fa(p) = y;
fa(x) = z, f[x].ch[!d] = y;
fa(y) = x, f[y].ch[d] = p;
Pushup(y, t), Pushup(x, t);
}
void Splay(int x, int t, int tar = 0) {
// tar 表示把 x 旋到 tar 的儿子
for (Pushdown(x, t); Nrt(x) && fa(x) != tar; Rot(x, t))
if (Nrt(fa(x)) && fa(fa(x)) != tar) Rot(Get(x) == Get(fa(x)) ? fa(x) : x, t);
}

然后是 Splice,我们再实现一个 Del 函数表示将没有中儿子的 Rake 节点删掉。

void Del(int x) { // x 是 Rake 节点
if (ls(x)) {
// 将 ls 与 rs 做 Splay 合并
int y = rig(ls(x));
Splay(y, 1, x), Set(rs(x), y, 1);
// 把 y 挂到 fa 的中儿子上并更新信息
Set(y, fa(x), 2);
Pushup(y, 1), Pushup(fa(x), 0), Clear(x);
} else {
// 没有左儿子,直接把 rs 挂到 fa 的中儿子上
Set(rs(x), fa(x), 2), Pushup(fa(x), 0), Clear(x);
}
}
void Splice(int x) { // x 是 Rake 节点
// 先把 x 旋到根,再把 fa 旋到根
Splay(x, 1);
int y = fa(x);
Splay(y, 0), Down(x, 1); // 这里 Down 是因为 Del 中要用到 ls 的 rig
if (rs(y)) {
// 交换 ms(x) 与 rs(fa)
swap(fa(ms(x)), fa(rs(y)));
swap(ms(x), rs(y));
Pushup(x, 1), Pushup(y, 0);
} else {
// 直接把 ms(x) 挂到 fa 的右儿子上,并删除没有中儿子的 x
Set(ms(x), y, 1), Del(x);
Pushup(y, 0);
}
}

然后是 Access

void Access(int x) { // x 是 Compress 节点
// 让 x 成为当前簇的端点
Splay(x, 0);
if (rs(x)) {
// 把 ms, rs 分别挂到新点的 ls, ms 上
int y = NewNode();
Set(ms(x), y, 0);
Set(rs(x), y, 2);
// 把新点挂到 x 的中儿子上
Set(y, x, 2);
Pushup(y, 1), Pushup(x, 0);
}
// 不断地 Splice 直到成为根
for (int y = x; fa(y); y = fa(y)) {
Splice(fa(y));
}
Splay(x, 0);
/*
上面的 4 行可以改为:
for (; fa(x); Splay(x, 0)) {
Splice(fa(x));
}
*/
}

接下来是一些动态操作:

int rt;
void Makert(int x) { // 让 x 成为根
Access(x), Rev(x);
}
int Find(int x) { // 找到 x 所在子树的根
return Access(x), lef(x);
}
void Split(int x, int y) { // 就是 Expose
Makert(x), Access(y);
}
void MPath(int x, int y, Tag w) {
// 路径修改
Split(x, y), PathTag(y, w);
}
void MSub(int x, Tag w) {
// 子树修改
Split(rt, x), f[x].val = f[x].val + w, SubTag(ms(x), w), Pushup(x, 0);
}
Dat QPath(int x, int y) {
// 路径查询
return Split(x, y), f[y].pat;
}
Dat QSub(int x) {
// 子树查询
return Split(rt, x), f[ms(x)].sub + Dat(1, f[x].val, f[x].val, f[x].val);
}
void Link(int x, int y) {
// 连接 x, y
Access(x), Makert(y), Set(y, x, 1), Pushup(x, 0);
}
void Chgfa(int x, int y) {
// 修改原树中 x 的父亲为 y
if (x == rt || x == y) return;
Split(rt, x);
int p = rig(ls(x)); // x 的原来父亲
ls(x) = fa(ls(x)) = 0, Pushup(x, 0); // 断开原来 x 与 p 的边
if (Find(x) == Find(y)) Link(x, p); // x 与 y 连通,那么重连 x 与 p
else Link(x, y); // 否则连接 x 与 y
}

注意主函数中一开始要令 cnt = n

完整代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 1e9;
struct Tag {
int k, b;
Tag(int k = 1, int b = 0) : k(k), b(b) {}
bool NE() { return k != 1 || b != 0; }
};
struct Dat {
int siz, min, max, sum;
Dat(int siz = 0, int min = inf, int max = -inf, int sum = 0) : siz(siz), min(min), max(max), sum(sum) {}
};
Tag operator+ (const Tag &x, const Tag &y) { return {x.k * y.k, x.b * y.k + y.b}; }
int operator+ (const int &x, const Tag &y) { return x * y.k + y.b; }
Dat operator+ (const Dat &x, const Tag &y) { return {x.siz, x.min == inf ? inf : x.min + y, x.max == -inf ? -inf : x.max + y, x.sum * y.k + x.siz * y.b}; }
Dat operator+ (const Dat &x, const Dat &y) { return {x.siz + y.siz, min(x.min, y.min), max(x.max, y.max), x.sum + y.sum}; }
struct Node {
int fa, ch[3];
int lef, rig, val;
Dat pat, sub;
Tag tpa, tsu;
bool rev;
void Clear() {
fa = ch[0] = ch[1] = ch[2] = lef = rig = val = 0;
rev = false, pat = sub = Dat(), tpa = tsu = Tag();
}
} f[1000005];
int cnt, tp, st[1000005];
int &ls(int x) { return f[x].ch[0]; }
int &ms(int x) { return f[x].ch[2]; }
int &rs(int x) { return f[x].ch[1]; }
int &fa(int x) { return f[x].fa; }
int &lef(int x) { return f[x].lef; }
int &rig(int x) { return f[x].rig; }
int NewNode() { return tp ? st[tp--] : ++cnt; }
int Get(int x) { return ls(fa(x)) == x ? 0 : (rs(fa(x)) == x ? 1 : 2); }
bool Nrt(int x) { return ls(fa(x)) == x || rs(fa(x)) == x; }
void Set(int x, int fx, int t) { if (x) f[fa(x)].ch[Get(x)] = 0, fa(x) = fx; f[fx].ch[t] = x; }
void Clear(int x) { f[x].Clear(), st[++tp] = x; }
void Rev(int x) { swap(ls(x), rs(x)), swap(lef(x), rig(x)), f[x].rev ^= 1; }
void PathTag(int x, Tag w) {
f[x].val = f[x].val + w;
f[x].pat = f[x].pat + w;
f[x].tpa = f[x].tpa + w;
}
void SubTag(int x, Tag w) {
f[x].sub = f[x].sub + w;
f[x].tsu = f[x].tsu + w;
}
void Down(int x, int t) {
if (!t) {
if (f[x].rev) Rev(ls(x)), Rev(rs(x)), f[x].rev = false;
if (f[x].tpa.NE()) PathTag(ls(x), f[x].tpa), PathTag(rs(x), f[x].tpa), f[x].tpa = Tag();
if (f[x].tsu.NE()) SubTag(ls(x), f[x].tsu), SubTag(rs(x), f[x].tsu), SubTag(ms(x), f[x].tsu), f[x].tsu = Tag();
} else {
if (f[x].tsu.NE()) {
SubTag(ls(x), f[x].tsu), SubTag(rs(x), f[x].tsu);
SubTag(ms(x), f[x].tsu), PathTag(ms(x), f[x].tsu);
f[x].tsu = Tag();
}
}
}
void Pushdown(int x, int t) { if (Nrt(x)) Pushdown(fa(x), t); Down(x, t); }
void Pushup(int x, int t) {
lef(x) = ls(x) ? lef(ls(x)) : x;
rig(x) = rs(x) ? rig(rs(x)) : x;
if (!t) {
f[x].pat = f[ls(x)].pat + Dat(1, f[x].val, f[x].val, f[x].val) + f[rs(x)].pat;
f[x].sub = f[ls(x)].sub + f[ms(x)].sub + f[rs(x)].sub;
} else {
f[x].sub = f[ls(x)].sub + f[rs(x)].sub + f[ms(x)].sub + f[ms(x)].pat;
}
}
void Rot(int x, int t) {
int y = fa(x), z = fa(y), d = Get(x), p = f[x].ch[!d];
if (z) f[z].ch[Get(y)] = x;
if (p) fa(p) = y;
fa(x) = z, f[x].ch[!d] = y;
fa(y) = x, f[y].ch[d] = p;
Pushup(y, t), Pushup(x, t);
}
void Splay(int x, int t, int tar = 0) {
for (Pushdown(x, t); Nrt(x) && fa(x) != tar; Rot(x, t))
if (Nrt(fa(x)) && fa(fa(x)) != tar) Rot(Get(x) == Get(fa(x)) ? fa(x) : x, t);
}
void Del(int x) { // R
if (ls(x)) {
int y = rig(ls(x));
Splay(y, 1, x), Set(rs(x), y, 1), Set(y, fa(x), 2), Pushup(y, 1), Pushup(fa(x), 0), Clear(x);
} else Set(rs(x), fa(x), 2), Pushup(fa(x), 0), Clear(x);
}
void Splice(int x) { // R
Splay(x, 1); int y = fa(x); Splay(y, 0), Down(x, 1);
if (rs(y)) swap(fa(ms(x)), fa(rs(y))), swap(ms(x), rs(y)), Pushup(x, 1), Pushup(y, 0);
else Set(ms(x), y, 1), Del(x), Pushup(y, 0);
}
void Access(int x) { // C
Splay(x, 0);
if (rs(x)) {
int y = NewNode();
Set(ms(x), y, 0), Set(rs(x), y, 2), Set(y, x, 2), Pushup(y, 1), Pushup(x, 0);
}
for (int y = x; fa(y); y = fa(y)) Splice(fa(y));
Splay(x, 0);
}
int rt;
void Makert(int x) { Access(x), Rev(x); }
int Find(int x) { return Access(x), lef(x); }
void Split(int x, int y) { Makert(x), Access(y); }
void MPath(int x, int y, Tag w) { Split(x, y), PathTag(y, w); }
void MSub(int x, Tag w) { Split(rt, x), f[x].val = f[x].val + w, SubTag(ms(x), w), Pushup(x, 0); }
Dat QPath(int x, int y) { return Split(x, y), f[y].pat; }
Dat QSub(int x) { return Split(rt, x), f[ms(x)].sub + Dat(1, f[x].val, f[x].val, f[x].val); }
void Link(int x, int y) { Access(x), Makert(y), Set(y, x, 1), Pushup(x, 0); }
void Chgfa(int x, int y) {
if (x == rt || x == y) return;
Split(rt, x);
int p = rig(ls(x));
ls(x) = fa(ls(x)) = 0, Pushup(x, 0);
if (Find(x) == Find(y)) Link(x, p);
else Link(x, y);
}
int n, m;
int eu[100005], ev[100005];
int main() {
scanf("%d%d", &n, &m), cnt = n;
for (int i = 1; i < n; ++i) scanf("%d%d", eu + i, ev + i);
for (int i = 1; i <= n; ++i) scanf("%d", &f[i].val), Pushup(i, 0);
for (int i = 1; i < n; ++i) Link(eu[i], ev[i]);
scanf("%d", &rt);
while (m--) {
int op, x, y, z;
scanf("%d", &op);
if (op == 0 || op == 5) {
scanf("%d%d", &x, &y);
MSub(x, {op != 0, y});
}
else if (op == 1) {
scanf("%d", &rt);
}
else if (op == 2 || op == 6) {
scanf("%d%d%d", &x, &y, &z);
MPath(x, y, {op != 2, z});
}
else if (op == 3 || op == 4 || op == 11) {
scanf("%d", &x);
Dat res = QSub(x);
if (op == 3) printf("%d\n", res.min);
else if (op == 4) printf("%d\n", res.max);
else printf("%d\n", res.sum);
}
else if (op == 7 || op == 8 || op == 10) {
scanf("%d%d", &x, &y);
Dat res = QPath(x, y);
if (op == 7) printf("%d\n", res.min);
else if (op == 8) printf("%d\n", res.max);
else printf("%d\n", res.sum);
}
else if (op == 9) {
scanf("%d%d", &x, &y);
Chgfa(x, y);
}
}
return 0;
}

简单萌萌哒 Top Tree(下) 还没写完,可以去看参考链接。


参考链接:

拓展阅读:

本文作者:Laijinyi

本文链接:https://www.cnblogs.com/laijinyi/p/18373391/Top-Tree-1

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   Laijinyi  阅读(203)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起