从ZOJ2114(Transportation Network)到Link-cut-tree(LCT)

【首先声明:LCT≠动态树,前者是一种数据结构,而后者是一类问题,即:LCT—解决—>动态树】

Link-cut-tree(下文统称LCT)是一种强大的数据结构,不仅可以像树链剖分一样对树上的两点进行询问(权值和、权值的最值……),还可以维护森林的连通性。

学习LCT首推杨哲神犇的《QTREE解法的一些研究》,很详细地解释了LCT的概念及实现

本文则以ZOJ2114一题为例,分析LCT实现过程中的一些事项,并且力求读者对LCT有一个“不次于‘感性’的认识”

叙述过程中会直接引用论文中的术语

题目大意:

N个点(N<=2e4),M次操作(M<=4e4),维护N个点构成的森林

最初N个点两两不连通

U x y z:新建一条权值为z的边,把x所在树的根节点连到y所在的树上

Q x y:询问x到y的距离。如果x和y未连通,输出Not connected.

思路:

对于Q操作,如果x和y所属的根节点不同,那么x和y必然是不连通的

对于U操作,首先需要找到x和y的根节点(分别记作u和v),然后把u连到v上

查询根节点可以用并查集

我们需要对森林进行修改(合并树)及询问操作,树链剖分是无法胜任的(树剖只能用于静态树),所以我们考虑LCT

我们用Splay维护Auxiliary Tree(如果不知道这是什么,请看论文):

 1 enum { Left=0,Right=1 };
 2
 3 struct SplayNode;
 4 SplayNode* vir;
 5
 6 struct SplayNode
 7 {
 8     int dist;
 9     int totDist;
10     bool isRoot;
11     SplayNode* parent;
12     SplayNode* child[2];
13 };

(为阅读方便,分割代码时作了一些修改)

vir的作用是代替空指针,从而免去很多不必要的判断

SplayNode类的成员变量如下:

dist:在森林(原图)中,该节点与父节点之间边的权值。

totDist:Auxiliary Tree中,以该节点为根的子树的dist之和

isRoot:该节点是否为所在Auxiliary Tree的根节点

child[]:该节点在Auxiliary Tree中的左右孩子。

parent:既可以是Auxiliary Tree内部的父节点,也可以是Auxiliary Tree整体的path-parent

(1)如果isRoot==false,那么parent表示该节点在Auxiliary Tree中的父节点

(2)如果isRoot==true,那么parent表示其所在Auxiliary Tree的path-parent,即其在森林中的父节点

也就是说,对于某个节点u,可能有多个节点以u为parent,但其中只有至多两个与u构成Auxiliary Tree。在编程实现中,这是一个值得注意的问题

如图,ABCDE构成森林中的一个连通分量,实线表示Preferred Child

相应的Auxiliary Tree(Series)。实线表示Preferred Child关系,虚线表示Path Parent关系

在Auxiliary Tree中,我们“隐式”地把节点的深度作为BST的关键字。也就是说,中序遍历一棵Auxiliary Tree,得到的就是一条从上到下的Preferred Path链

(如果你不能理解从图1到图2的转换,复习一下LCT的相关定义)

之后是Splay的一些操作(成员函数):

 1     void init()
 2     {
 3         dist=totDist=0;
 4         isRoot=true;
 5         parent=child[0]=child[1]=vir;
 6     }
 7
 8     void update()
 9     {
10         this->totDist =
11                 child[Left]->totDist+child[Right]->totDist+this->dist;
12     }
13
14     void rotate(int dir)
15     //If dir==Right then rotate clockwise, else rotate counter-clockwise
16     {
17         if(parent==parent->parent->child[Left])
18             parent->parent->child[Left]=this;
19         else if(parent==parent->parent->child[Right])
20             parent->parent->child[Right]=this;
21
22         parent->child[dir^1]=this->child[dir];
23         child[dir]->parent=this->parent;
24
25         child[dir]=parent;
26         parent=parent->parent;
27         child[dir]->parent=this;
28     }
29
30     void zigzig(int dir)
31     {
32         parent->rotate(dir);
33         this->rotate(dir);
34
35         child[dir]->child[dir]->update();
36         child[dir]->update();
37         this->update();
38     }
39
40     void zigzag(int dir) //dir depends on first rotation
41     {
42         rotate(dir);
43         rotate(dir^1);
44
45         child[Left]->update();
46         child[Right]->update();
47         this->update();
48     }
49
50     void zig(int dir)
51     {
52         rotate(dir);
53         child[dir]->update();
54         this->update();
55     }
56
57     void splay()
58     {
59         while(!isRoot)
60         {
61             int status=0;
62             if(this==parent->child[Left]) status|=1;
63             else status|=2;
64
65             if(!parent->isRoot)
66             {
67                 if(parent->parent->isRoot) this->isRoot=true;
68                 if(parent==parent->parent->child[Left]) status|=4;
69                 else status|=8;
70             }
71             else
72                 this->isRoot=true;
73
74             switch(status)
75             {
76             case 1: zig(Right);
77                     child[Right]->isRoot=false;
78                     break;
79             case 2: zig(Left);
80                     child[Left]->isRoot=false;
81                     break;
82             case 5: zigzig(Right);
83                     if(isRoot) child[Right]->child[Right]->isRoot=false;
84                     break;
85             case 6: zigzag(Left);
86                     if(isRoot) child[Right]->isRoot=false;
87                     break;
88             case 9: zigzag(Right);
89                     if(isRoot) child[Left]->isRoot=false;
90                     break;
91             case 10:zigzig(Left);
92                     if(isRoot) child[Left]->child[Left]->isRoot=false;
93                     break;
94             default:break;
95             }
96         }
97     }

各函数的说明:

init:初始化。令该节点成为独立的一个连通分量。

update:(在旋转以后)更新节点的totDist值。

rotate:将节点上旋。如果dir==Right,那么沿顺时针方向上旋;如果dir==Left,那么沿逆时针方向上旋。

zig,zigzig,zigzag:Splay的单双旋

splay:提根,将当前节点提到所在Auxiliary Tree的根部

17         if(parent==parent->parent->child[Left])
18             parent->parent->child[Left]=this;
19         else if(parent==parent->parent->child[Right])
20             parent->parent->child[Right]=this;

这里的判断需要特别注意。必须写成else if的形式(还记得我们是怎样定义parent的吗?)

另外splay的过程中涉及到isRoot的变化

除此之外其他的部分和普通的SplayTree大同小异,在这里就不多说了,详见代码。

至此,LCT的基础——SplayNode就已经打好了,接下来就是LCT的精华:Access(也有的版本称为Expose)操作:

 1 int access(SplayNode* u)
 2 {
 3     SplayNode* v=vir;
 4     int res=0;
 5     while(u!=vir)
 6     {
 7         u->splay();
 8         u->child[Right]->isRoot=true;
 9         res=u->child[Right]->totDist;
10         u->child[Right]=v;
11         v->isRoot=false;
12         u->update();
13         v=u; u=u->parent;
14     }
15     return res + v->child[Right]->totDist;
16 }
17
18 inline int query(SplayNode* u,SplayNode* v)
19 {
20     access(u);
21     return access(v);
22 }

比起SplayNode的实现是不是短了许多?(斜眼笑

然而,正所谓浓缩就是精华,Access的原理可以说是不容小视的(毕竟是LCT所有操作的基石)

接下来我们以此图(以下称为原图)为例,演示access(H)的全过程。图为access(H)之前

access(H)之后的效果图,可以看到途经的所有节点,其原来的Preferred Path都被切断了,取而代之的是“直达”H的Preferred Path

access(H)之前的Auxiliary Tree,也是我们操作的对象。

首先我们splay(H),把H提到所在Auxiliary Tree的根部

此时H的右子树对应原图中的Preferred Path

由于新的Preferred Path要“切断”沿途所有旧的Preferred Child,而H的右子树对应H原来的Preferred Child

所以H和I之间要断开,表示新的Preferred Path到H就中止了,以I为根的子树成为一棵新的Auxiliary Tree

之后别忘了update一下H的totDist

为了将A和H用Preferred Path相连,我们还要对H的Path Parent,也就是对C“开刀”

首先还是splay(C)。当然C已经是Auxiliary Tree的根节点了,所以对于本例,splay(C)什么都没做

然后依然将C与右子树D断开。

之后,我们要将C的Auxiliary Tree与H的Auxiliary Tree相连。由于H的Auxiliary Tree在原图中处于C的下方,所以H应该成为C的新的右孩子

如图所示,A和E已经用Preferred Path相连了。至此access(H)过程已经完成。不妨把此时的Auxiliary Tree和上面的效果图对比一下

当然在编程时我们需要一个判断条件:C的parent为vir,再往上走就没有任何意义了,所以access操作停止

……

那么,access的返回值是干嘛的?totDist到底有什么卵用?

举个栗子:询问D到H的距离。我们已经进行了access(H)

我们给每条边赋予一个权值(图中用黑色标注)

但在LCT的Auxiliary Tree中,这个权值实际上是存储到“下方”的节点中的(图中以红色标注)。括号中是totDist,括号外是dist

然后我们access(D)

这是access(D)之后的Auxiliary Tree

在原图中,D到F的距离可以分解成D到C的距离和C到H的距离。之所以选择C是因为C是D和F的最近公共祖先(LCA)

而在Auxiliary Tree中,这恰好对应C的新、旧右孩子的totDist之和

H是C的”旧“右孩子,被D取代后,D就成了C的”新“右孩子

在编程过程中,我们需要设法保存”旧“右孩子的totDist值(要不然就失联了( ̄▽ ̄")),access函数中的res变量恰好胜任了这一要求

query函数中进行了两次access操作,第一次的返回值因为没有意义所以被丢弃了,而第二次的返回值就是询问的结果

问题已经解决了一半,接下来就是两个子树的合并(并不难):

 1 const int maxN=20005;
 2 int center[maxN];
 3
 4 int getCenter(int x)
 5 {
 6     return center[x]==x ? x :
 7            center[x]=getCenter(center[x]);
 8 }
 9
10 inline void link(int u,int v,int len)
11 {
12     access(node+u);
13     node[u].splay();
14     center[u]=v;
15     node[u].parent=node+v;
16     node[u].dist=len;
17     node[u].update();
18 }

center就是并查集,合并之前,首先要用getCenter函数找到两棵树的根节点u和v,然后把u连到v上

首先access(u),然后splay(u)

splay的目的是让u的parent成为vir,这样在连接u和v时,不会切断u所在的Auxiliary Tree

于是这道题用LCT完美解决(而且效率很高),附上完整代码:

  1 #include <cstdio>
  2 #include <cstring>
  3 #include <algorithm>
  4
  5 enum { Left=0,Right=1 };
  6
  7 struct SplayNode;
  8 SplayNode* vir;
  9
 10 struct SplayNode
 11 {
 12     int dist;
 13     int totDist;
 14     bool isRoot;
 15     SplayNode* parent;
 16     SplayNode* child[2];
 17
 18     void init()
 19     {
 20         dist=totDist=0;
 21         isRoot=true;
 22         parent=child[0]=child[1]=vir;
 23     }
 24
 25     void update()
 26     {
 27         this->totDist =
 28                 child[Left]->totDist+child[Right]->totDist+this->dist;
 29     }
 30
 31     void rotate(int dir)
 32     //If dir==Right then rotate clockwise, else rotate counter-clockwise
 33     {
 34         if(parent==parent->parent->child[Left])
 35             parent->parent->child[Left]=this;
 36         else if(parent==parent->parent->child[Right])
 37             parent->parent->child[Right]=this;
 38
 39         parent->child[dir^1]=this->child[dir];
 40         child[dir]->parent=this->parent;
 41
 42         child[dir]=parent;
 43         parent=parent->parent;
 44         child[dir]->parent=this;
 45     }
 46
 47     void zigzig(int dir)
 48     {
 49         parent->rotate(dir);
 50         this->rotate(dir);
 51
 52         child[dir]->child[dir]->update();
 53         child[dir]->update();
 54         this->update();
 55     }
 56
 57     void zigzag(int dir) //dir depends on first rotation
 58     {
 59         rotate(dir);
 60         rotate(dir^1);
 61
 62         child[Left]->update();
 63         child[Right]->update();
 64         this->update();
 65     }
 66
 67     void zig(int dir)
 68     {
 69         rotate(dir);
 70         child[dir]->update();
 71         this->update();
 72     }
 73
 74     void splay()
 75     {
 76         while(!isRoot)
 77         {
 78             int status=0;
 79             if(this==parent->child[Left]) status|=1;
 80             else status|=2;
 81
 82             if(!parent->isRoot)
 83             {
 84                 if(parent->parent->isRoot) this->isRoot=true;
 85                 if(parent==parent->parent->child[Left]) status|=4;
 86                 else status|=8;
 87             }
 88             else
 89                 this->isRoot=true;
 90
 91             switch(status)
 92             {
 93             case 1: zig(Right);
 94                     child[Right]->isRoot=false;
 95                     break;
 96             case 2: zig(Left);
 97                     child[Left]->isRoot=false;
 98                     break;
 99             case 5: zigzig(Right);
100                     if(isRoot) child[Right]->child[Right]->isRoot=false;
101                     break;
102             case 6: zigzag(Left);
103                     if(isRoot) child[Right]->isRoot=false;
104                     break;
105             case 9: zigzag(Right);
106                     if(isRoot) child[Left]->isRoot=false;
107                     break;
108             case 10:zigzig(Left);
109                     if(isRoot) child[Left]->child[Left]->isRoot=false;
110                     break;
111             default:break;
112             }
113         }
114     }
115 };
116
117 int access(SplayNode* u)
118 {
119     SplayNode* v=vir;
120     int res=0;
121     while(u!=vir)
122     {
123         u->splay();
124         u->child[Right]->isRoot=true;
125         res=u->child[Right]->totDist;
126         u->child[Right]=v;
127         v->isRoot=false;
128         u->update();
129         v=u; u=u->parent;
130     }
131     return res + v->child[Right]->totDist;
132 }
133
134 inline int query(SplayNode* u,SplayNode* v)
135 {
136     access(u);
137     return access(v);
138 }
139
140 void initVir()
141 {
142     vir=new SplayNode;
143     vir->init();
144 }
145
146 const int maxN=20005;
147
148 SplayNode node[maxN];
149 int center[maxN];
150 int N,Q;
151 int lastRes=0;
152
153 inline void link(int u,int v,int len)
154 {
155     access(node+u);
156     node[u].splay();
157     center[u]=v;
158     node[u].parent=node+v;
159     node[u].dist=len;
160     node[u].update();
161 }
162
163 inline void initNode()
164 {
165     for(int i=1;i<=N;i++) node[i].init();
166     for(int i=1;i<=N;i++) center[i]=i;
167 }
168
169 int getCenter(int x)
170 {
171     return center[x]==x ? x :
172            center[x]=getCenter(center[x]);
173 }
174
175 #define DEBUG
176 #undef DEBUG
177
178 #ifdef DEBUG
179 int qc=0;
180 #endif
181
182 void solve()
183 {
184     lastRes=0;
185     scanf("%d%d",&N,&Q);
186     initNode();
187     char cmd;
188     int v1,v2,len;
189     while(Q--)
190     {
191         do cmd=getchar(); while(cmd==‘ ‘ || cmd==‘\n‘);
192         if(cmd==‘Q‘)
193         {
194             scanf("%d%d",&v1,&v2);
195             if(getCenter(v1)==getCenter(v2))
196                 printf("%d\n",lastRes=query(node+v1,node+v2));
197             else printf("Not connected.\n");
198 #ifdef DEBUG
199             printf("#%d query successful.\n",++qc);
200 #endif
201         }
202         else
203         {
204             scanf("%d%d%d",&v1,&v2,&len);
205 #ifndef DEBUG
206             v1=(v1-lastRes-1)%N;
207             if(v1<=0) v1+=N;
208             v2=(v2-lastRes-1)%N;
209             if(v2<=0) v2+=N;
210 #endif
211             link(getCenter(v1),getCenter(v2),len);
212         }
213     }
214 }
215
216 int main()
217 {
218     initVir();
219     int X; scanf("%d",&X);
220     while(X--) solve();
221     return 0;
222 }

Problem:ZOJ P2114

时间: 2024-10-06 00:29:11

从ZOJ2114(Transportation Network)到Link-cut-tree(LCT)的相关文章

Codeforces Round #339 (Div. 2) A. Link/Cut Tree

A. Link/Cut Tree Programmer Rostislav got seriously interested in the Link/Cut Tree data structure, which is based on Splay trees. Specifically, he is now studying the expose procedure. Unfortunately, Rostislav is unable to understand the definition

F - Link/Cut Tree CodeForces - 614A(水题)

Programmer Rostislav got seriously interested in the Link/Cut Tree data structure, which is based on Splay trees. Specifically, he is now studying the expose procedure. Unfortunately, Rostislav is unable to understand the definition of this procedure

AC日记——【模板】Link Cut Tree 洛谷 P3690

[模板]Link Cut Tree 思路: LCT模板: 代码: #include <bits/stdc++.h> using namespace std; #define maxn 300005 int n,m,val[maxn]; int top,ch[maxn][2],f[maxn],xr[maxn],q[maxn],rev[maxn]; inline void in(int &now) { int if_z=1;now=0; char Cget=getchar(); while

bzoj2049 [Sdoi2008]Cave 洞穴勘测 link cut tree入门

link cut tree入门题 首先说明本人只会写自底向上的数组版(都说了不写指针.不写自顶向下QAQ……) 突然发现link cut tree不难写... 说一下各个函数作用: bool isroot(int x):判断x是否为所在重链(splay)的根 void down(int x):下放各种标记 void rotate(int x):在x所在重链(splay)中将x旋转到fa[x]的位置上 void splay(int x):在x坐在重链(splay)中将x旋转到根 void acce

脑洞大开加偏执人格——可持久化treap版的Link Cut Tree

一直没有点动态树这个科技树,因为听说只能用Splay,用Treap的话多一个log.有一天脑洞大开,想到也许Treap也能从底向上Split.仔细思考了一下,发现翻转标记不好写,再仔细思考了一下,发现还是可以写的,只需要实时交换答案二元组里的两棵树,最后在吧提出来的访问节点放回去就行了.本着只学一种平衡树的想法,脑洞大开加偏执人格的开始写可持久化Treap版的Link Cut Tree... 写了才发现,常数硕大啊!!!代码超长啊!!!因为merge是从上到下,split从下到上,pushdow

link cut tree 入门

鉴于最近写bzoj还有51nod都出现写不动的现象,决定学习一波厉害的算法/数据结构. link cut tree:研究popoqqq那个神ppt. bzoj1036:维护access操作就可以了. #include<cstdio> #include<cstring> #include<cctype> #include<algorithm> #include<queue> using namespace std; #define rep(i,s,

HDOJ 题目3966 Aragorn&#39;s Story(Link Cut Tree成段加减点权,查询点权)

Aragorn's Story Time Limit: 10000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total Submission(s): 5505    Accepted Submission(s): 1441 Problem Description Our protagonist is the handsome human prince Aragorn comes from The Lor

LuoguP3690 【模板】Link Cut Tree (动态树) LCT模板

P3690 [模板]Link Cut Tree (动态树) 题目背景 动态树 题目描述 给定n个点以及每个点的权值,要你处理接下来的m个操作.操作有4种.操作从0到3编号.点从1到n编号. 0:后接两个整数(x,y),代表询问从x到y的路径上的点的权值的xor和.保证x到y是联通的. 1:后接两个整数(x,y),代表连接x到y,若x到y已经联通则无需连接. 2:后接两个整数(x,y),代表删除边(x,y),不保证边(x,y)存在. 3:后接两个整数(x,y),代表将点x上的权值变成y. 输入输出

Link Cut Tree学习笔记

从这里开始 动态树问题和Link Cut Tree 一些定义 access操作 换根操作 link和cut操作 时间复杂度证明 Link Cut Tree维护链上信息 Link Cut Tree维护子树信息 小结 动态树问题和Link Cut Tree 动态树问题是一类要求维护一个有根树森林,支持对树的分割, 合并等操作的问题. Link Cut Tree(林可砍树?简称LCT)是解决这一类问题的一种数据结构. 一些无聊的定义 Link Cut Tree维护的是动态森林中每棵树的任意链剖分. P

P3690 【模板】Link Cut Tree (动态树)

P3690 [模板]Link Cut Tree (动态树) https://www.luogu.org/problemnew/show/P3690 分析: LCT模板 代码: 注意一下cut! 1 #include<cstdio> 2 #include<algorithm> 3 4 using namespace std; 5 6 const int N = 300100; 7 8 int val[N],fa[N],ch[N][2],rev[N],sum[N],st[N],top;