(你看到的是2019/9/19的 revision)
缘起
当年 naive 的 EtaoinWu 在网上看了一些奇奇怪怪的东西,就写了这篇东西来讲一讲。现在发现小坑真的多,就来改一改再拿出来。这个数据结构可以用于维护子树信息,但无法简单地维护树链信息;由于比 multi-level partition top tree 简短,因此可以拿来做动态图连通性。
相关参考资料
Stanford 数据结构课程 讲稿这篇英语讲稿的前半部分讲的比较好, 很有建设性. 其后半部分是动态图连通性,准备在19年9月更新出来。
例题大意是n点的图, 初始是完全图, 每次查询加边删边, 之后输出是否是森林, 看AC记录中代码最长的就是ETT的解, 样例代码如下
ETT 简介
树 (无根树, 即没有环的联通无向图) 正常情况下没有欧拉回路, 但是把每条无向边{u,v}变为一条(u,v)的有向边与一条(v,u)的有向边之后, 就具有了欧拉回路, 定义这个欧拉回路为“树的欧拉回路”. 伪代码如下:
void euler_tour(node *now) {
(now->data)
printfor(son in now.sons) {
(son)
euler_tour(now->data)
print}
}
使用这样的方式, 就可以将树转化为一条链, 长度小于等于 2n. 而维护原来的树, 可以转化为维护这个欧拉回路. 维护一条链是相对简单的任务,有各种东西可以完成,比如平衡树。
首先, 让我们分析一下 link & cut & is_connected 在 Euler tour 上的影响.
link
首先看link.
上图中虚线连接的两棵树的欧拉回路分别是a b d b c e c b
/
f g j k j i j g h g
, 执行link(a,f)
而得到的新树的欧拉回路是a b d b c e c b a f g j k j i j g h g f
,
就是将二者直接合并起来. 这里用到了区间合并的操作.
请注意, 这里的欧拉回路中删去了最后一个, 仅仅为了代码方便
事实上这是体现了 用边来维护欧拉序的意义下 每条边的始点的位置
比如, 上图虚线左侧的树的欧拉回路 (利用上述伪代码) 得到的应该为a b d b c e c b a
但是这个回路的第一项与最后一项是相同的, 直接将最后一项删除以便维护. 这也更符回路的定义.
而如果上述树是b a b c e c b d
/
j k j i j g h g f g
,
那么把两个回路的任意一个 a/f节点找出来,
在它们之前将它们断开 (A=b
/
B=a b c e c b d
/ C=j k j i j g h g
/
D=f g
) , 新建a与f,
然后按A newa D C newf B
的顺序连接起来,
得到b a f g j k j i j g h g f a b c e c b d
,
这与前段得到的树是一样的.
而split一个线段、merge两个线段的复杂度取决于我们储存Euler Tour的数据结构.
cut
至于cut, 就用刚才那个link的逆过程吧. 对于在根上的cut,
一开始是a b d b c e c b a f g j k j i j g h g f
,
直接断开中间的a与f,
得到的就是原来的那两个回路a b d b c e c b
/
f g j k j i j g h g
.
对于不在根上的cut,
比如在b a f g j k j i j g h g f a b c e c b d
上执行cut(a,f),
首先要找到回路中a f
的位置与f a
的位置,
然后将这两个位置断开, 得到b a
f g j k j i j g h g f
a b c e c b d
,
将断开位置左侧的两个点删去, 得到A=b
B=f g j k j i j g h g
C=a b c e c b d
,
然后将A与C合并, 就得到了新的两棵树.
is_connected
判断两者是否联通, 就等于是判断二者是否在同一个欧拉回路内. 直接找到一个节点的任意一个node, 然后走到根 \(O(\log n)\), 如果root相同那么就在一个联通块中.
实现细节
观察以上文中我使用了斜体的地方, 这些地方指示了我们需要做出哪些索引工作:
- 找到一个树中节点在ETT中的任意一个点
- 找到两个点之间的位置, 即: 找到一条树中的边在ETT中的位置
所以, 我们有了以下两种方案:
- 对于每个 ETTNode, 存储原图上的一个边. 然而这样就没法找节点, 那么有一个小trick: 为每一个点增加一个自环, 仍然维护欧拉回路, 存储完毕之后使用二叉树或者哈希做索引;
- 对于每个 ETTNode, 储存原图上的一个点, 一个点就有多个位置,
索引数组使用
vector<map<int, node *>>
,v[i][j]
表示储存了i的值、欧拉回路上后继是j号节点的那个node, 这样实现比较好写, 空间常数较小.
最后实现
最终代码里使用了非旋转 Treap 做储存欧拉回路的平衡树, 非旋转 Treap 使用的 merge / split 操作正好适合ETT的作用.
代码很长,也可以在 LOJ#122 上找到代码;各路神仙有自己的写法,可以参照着看,我的实现并不特别优秀。
[SDOI2008]洞穴勘测 裸题 程序
显然, 这题可以LCT做, 但是也可以当做一道ETT的题. 代码如下: (常数大2~3倍)
#include <bits/stdc++.h>
using namespace std;
struct fastIO {
static const int BUF_SIZE = 1 << 15;
char inbuf[BUF_SIZE];
char outbuf[BUF_SIZE];
int incur, outcur;
FILE *in, *out;
():incur(BUF_SIZE),outcur(0),in(stdin),out(stdout) {}
fastIO(const fastIO &iio):incur(BUF_SIZE),outcur(0),in(iio.in),out(iio.out) {}
fastIO~fastIO() {close();}
inline fastIO &operator=(const fastIO &iio) {incur = (BUF_SIZE),outcur = (0),in = (iio.in),out = (iio.out);return *this;}
inline char getchar() {if (incur == BUF_SIZE) {fread(inbuf, BUF_SIZE, 1, in);incur = 0;}return inbuf[incur++];}
inline void getstr(char *str) {*str = getchar();while(!isgraph(*str))*str = getchar();while(isgraph(*str))*++str = getchar();*str = 0;}
inline int getint() {int x = 0;char c = getchar();while (!isdigit(c))c = getchar();while (isdigit(c)) {x = x * 10 + c - '0';c = getchar();}return x;}
inline void putchar(char ch) {outbuf[outcur++] = ch;if (outcur == BUF_SIZE) {fwrite(outbuf, BUF_SIZE, 1, out);outcur = 0;} }
inline void putint(int x) {if (x >= 10) putint(x / 10);putchar(x % 10 + '0');}
inline void close() {if (outcur > 0) fwrite(outbuf, outcur, 1, out);outcur = 0;}
} IO;
unsigned long rng(unsigned long e = 0)
{
static unsigned long t = 0, x = 123456789, y = 362436069, z = 521288629, w = 88675123, v = 5783321, d = 6615241;
+= e; x += d; t = (x ^ (x >> 2)); x = y; y = z; z = w; w = v; v = (v ^ (v << 4)) ^ (t ^ (t << 1));
d return e ? (((d += 362437) + v) % e) : ((d += 362437) + v);
}
struct EulerTourTree {
struct node {
*lch = nullptr;
node *rch = nullptr;
node *parent = nullptr;
node int size = 1;
int vid;
bool active = false;
int sub = 0;
(int vid) : vid(vid) {}
node};
unsigned long long xor_shift() {
static unsigned long long x = time(NULL);
^= x << 13; x ^= x >> 7; x ^= x << 17;
x return x;
}
size_t size(node *x) {
return x != nullptr ? x->size : 0;
}
size_t sub(node *x) {
return x != nullptr ? x->sub : 0;
}
*update(node *x) {
node if (x == nullptr) return x;
->size = 1 + size(x->lch) + size(x->rch);
x->sub = sub(x->lch) + sub(x->rch);
xif (x->active) x->sub++;
return x;
}
void update_ancestor(node *x) {
if (x == nullptr) return;
= update(x);
x (x->parent);
update_ancestor}
void activate(node *x, bool value) {
if (x == nullptr) return;
->active = value;
x(x);
update_ancestor}
*merge(node *x, node *y) {
node if (x == nullptr) return y;
if (y == nullptr) return x;
if (xor_shift() % (size(x) + size(y)) < size(x)) {
->rch = merge(x->rch, y);
xif (x->rch != nullptr) x->rch->parent = x;
return update(x);
} else {
->lch = merge(x, y->lch);
yif (y->lch != nullptr) y->lch->parent = y;
return update(y);
}
}
<node *, node *> split(node *x, size_t k) {
pairif (x == nullptr) return make_pair(nullptr, nullptr);
if (k <= size(x->lch)) {
auto p = split(x->lch, k);
->lch = p.second;
xif (p.first != nullptr) p.first->parent = nullptr;
if (p.second != nullptr) p.second->parent = x;
return make_pair(p.first, update(x));
} else {
auto p = split(x->rch, k - size(x->lch) - 1);
->rch = p.first;
xif (p.first != nullptr) p.first->parent = x;
if (p.second != nullptr) p.second->parent = nullptr;
return make_pair(update(x), p.second);
}
}
*root(node *x) {
node if (x == nullptr) return x;
if (x->parent == nullptr) return x;
return root(x->parent);
}
int index_of(node *x) {
if (x == nullptr) return 0;
int result = -1;
bool l = true;
while (x != nullptr) {
if (l) result += 1 + size(x->lch);
if (x->parent == nullptr) break;
= x->parent->rch == x;
l = x->parent;
x }
return result;
}
void connected_component(node *x, vector<int> &res) {
if (x == nullptr) return;
if (x->active) res.push_back(x->vid);
(x->lch, res);
connected_component(x->rch, res);
connected_component}
<int> connected_component(int u) {
vector*x = root(any_node(u));
node if (x == nullptr) return{ u };
<int> res;
vector(x, res);
connected_componentreturn res;
}
<map<int, node *>> tr;
vector
(int n) : tr(n) {}
EulerTourTree
*any_node(int u) {
node return tr[u].empty() ? nullptr : tr[u].begin()->second;
}
bool link(int u, int v) {
*x = any_node(u);
node *y = any_node(v);
node
*root_x = root(x);
node *root_y = root(y);
node
if (root_x != nullptr && root_x == root_y) return false;
*A, *B, *C, *D;
node (A, B) = split(root_x, index_of(x));
tie(C, D) = split(root_y, index_of(y));
tie
// AB, CD => A (u->v) D C (v->u) B
*uv = new node(u);
node *vu = new node(v);
node
if (tr[u].empty()) activate(uv, true);
if (tr[v].empty()) activate(vu, true);
tr[u][v] = uv;
tr[v][u] = vu;
= merge(A, uv);
A = merge(A, D);
A = merge(A, C);
A = merge(A, vu);
A = merge(A, B);
A
return true;
}
bool cut(int u, int v) {
if (tr[u].count(v) == 0) return false;
*uv = tr[u][v];
node *vu = tr[v][u];
node tr[u].erase(v);
tr[v].erase(u);
if (uv->active) {
(uv, false);
activate(any_node(u), true);
activate}
if (vu->active) {
(vu, false);
activate(any_node(v), true);
activate}
*rt = root(uv);
node
int index_uv = index_of(uv);
int index_vu = index_of(vu);
if (index_uv > index_vu) swap(index_uv, index_vu);
*A, *B;
node auto p = split(rt, index_vu);
= split(p.second, 1).second;
B auto q = split(p.first, index_uv);
= q.first;
A auto r = split(q.second, 1);
(B, A);
merge
return true;
}
bool is_connected(int u, int v) {
if (u == v) return true;
*x = any_node(u);
node *y = any_node(v);
node return x != nullptr && root(x) == root(y);
}
int sub(int u) {
*x = any_node(u);
node if (x == nullptr) return 1;
return sub(root(x));
}
};
char c[1111];
signed main()
{
int n,m;
= IO.getint();
n = IO.getint();
m (n+1);
EulerTourTree ETTfor (int i = 1; i <= m; i++) {
int a1, b1;
.getstr(c);
IO= IO.getint();
a1 = IO.getint();
b1 if (c[0] == 'Q') {
((ETT.is_connected(a1,b1))?"Yes\n":"No\n");
printf} else if (c[0] == 'C') {
.link(a1, b1);
ETT} else if (c[0] == 'D') {
.cut(a1, b1);
ETT}
}
return 0;
}