动态树问题, 即要求我们维护一个由若干棵子结点无序的有根树组成的森林。
要求这个数据结构支持对树的分割、合并,对某个点到它的根的路径的某些操作,以及对某个点的子树进行的某些操作。
在这里我们考虑一个简化的动态树问题,它只包含对树的形态的操作和对某个点到根的路径的操作:
维护一个数据结构,支持以下操作:
• MAKE TREE() — 新建一棵只有一个结点的树。
• CUT(v) — 删除 v 与它的父亲结点 parent(v) 的边,相当于将点 v 的子树分离了出来。
• JOIN(v,w) — 让 v 成为 w 的新的儿子。其中 v 是一棵树的根结点,并且 v 和 w 是不同的两棵树中的结点。
• FIND ROOT(v) — 返回 v 所在的树的根结点。搞清了这个问题,我们也容易扩充这个数据结构,维护每个点到它所属的树的根结点的路径的一些信息,例如权和、边权的最大值、路径长度等。
Link-Cut Trees 是由 Sleator 和 Tarjan 发明的解决这类动态树问题的一种数据结构。
这个数据结构可以在均摊 O(logn) 的时间内实现上述动态树问题的每个操作。
如果没有用对树的形态的改变的话,我们可以用树链剖分,把树剖分成许多条链并维护上面的权值信息。
然而对于树的形态的改变,树链的剖分方案也要改变,我们借助 Splay 的思想来动态维护许多树链(称为 Link-Cut Trees)。
称一个点被访问过,如果刚刚执行了对这个点的 ACCESS 操作。如果结点 v 的子树中,最后被访问的结点在子树 w 中,这里 w 是 v 的儿子, 那么就称 w 是 v 的 Preferred Child。如果最后被访问过的结点就是 v 本身,那么它没有 Preferred Child。每个点到它的 Preferred Child 的边称作 Preferred Edge。由 Preferred Edge 连接成的不可再延伸的路径称为 Preferred Path。这样,整棵树就被划分成了若干条 Preferred Path。对每条 Preferred Path,用这条路上的点的深度作为关键字,用一棵平衡树来维护它(在这棵平衡树中,每个点的左子树中的点,都在 Preferred Path 中这个点的上方;右子树中的点,都在 Preferred Path 中这个点的下方)。需要注意的是,这种平衡树必须支持分离与 合并。这里,我们选择 Splay Tree 作为这个平衡树的数据结构。我们把这棵平衡树称为一棵 Auxiliary Tree。知道了树 T 分解成的这若干条 Preferred Path,我们只需要再知道这些路径之间的连接关系,就可以表示出这棵树 T。用 Path Parent 来记录每棵 Auxiliary Tree 对应的 Preferred Path 中的最高点的父亲结点,如果这个 Preferred Path 的最高点就是根结点,那么令这棵 Auxiliary Tree 的 Path Parent 为 null。Link-Cut Trees 就是将要维护的森林中的每棵树 T 表示为若干个 Auxiliary Tree。并通过 Path Parent 将这些 Auxiliary Tree 连接起来的数据结构。
实际上,对于定义要从两方面去理解:
首先是逻辑结构,存在着一棵或多棵树,这是在逻辑上存在的,我们要做的就是对这个森林进行操作与维护;
其次是物理结构,我们并没有直接储存这些树,而是把树剖分成了多条链,每条链作为一棵 Splay。Splay 中的最小结点就是链的头结点。
回顾一下定义:
ACCESS(访问):又叫 Expose,对结点的访问操作。
Preferred Child(最佳孩子):最近一次访问过的儿子。最近一次被访问过的结点没有 Preferred Child。
Preferred Edge(最佳边):每个结点到 Preferred Child 的边。
Preferred Path(最佳路径):连续的 Preferred Edge 组成的边。
Auxiliary Tree(辅助树):在 Splay 上维护一条链,对于 Splay 上的每个结点,它的左子树上的点都在链的上方,右子树的点都在链的下方。
Path Parent(路径的父亲):记录链上最高结点的父亲,也就是 Splay 最左边结点的父亲,用于表示链与链之间的关系。
ACCESS 操作是 Link-Cut Trees 的所有操作的基础。假设调用了过程 ACCESS(v),那么从点 v 到根结点的路径就成为一条新的 Preferred Path。如果路径上经过的某个结点 u 并不是它的父亲 parent(u) 的 Preferred Child,那么由于 parent(u) 的 Preferred Child 会变为 u,原本包含 parent(u) 的 Preferred Path 将不再包含结点 parent(u) 及其之上的部分。
也就是说,过程 ACCESS 会改变逻辑上的树上的链剖分,它将结点 v 到根结点的路径作为一条新的链。而这条链会把旧的链切割开。
在物理上的 Splay 的表现就是,Splay 中的一些树被断开与重组。
下图为 Link-Cut Trees 的一个结构示意图,及一次 ACCESS 操作的前后对比图。
首先,由于访问了点 v,那么它的 Preferred Child 应当消失。先将点 v 旋转到它所属的 Auxiliary Tree 的根,如果 v 在 v 所属的 Auxiliary Tree 中有右儿子(也就是 v 原来的 Preferred Child), 那么应该将 v 在 v 所属的 Auxiliary Tree 中的右子树(对应着它的原来的 Preferred Child 之下的 Preferred Path)从 v 所属的 Auxiliary Tree 中分离,并设置这个新的 Auxiliary Tree 的 Path Parent 为 v。然后,如果点 v 所属的 Preferred Path 并不包含根结点,设它的 Path Parent 为 u,那么需要将 u 旋转 到 u 所属的 Auxiliary Tree 的根,并用点 v 所属的 Auxiliary Tree 替换到点 u 所属的 Auxiliary Tree 中 点 u 的右子树,再将原来点 u 所属的 Auxiliary Tree 中点 u 的右子树的 Path Parent 设置为 u。如此操作,直到到达包含根结点的 Preferred Path。
这里说的是对 Splay 的具体操作,对于访问的结点 v,伸展它到根,这样左子树的点在链的上方,右子树的点在链的下方。断开右子树即断开 v 在原树上与下方链的连接。
之后在 Splay 上将链的父结点 u 伸展到根,用 v 替换 u 的右子树,表现在逻辑树上就是将 u 下方的旧链替换成我们新的链。
在具体实现的时候,Splay 上的父亲与 Path Parent 可以在同一个数组上存储,当一个结点是它父结点的某个儿子时,它的父结点是 Splay 上的父结点,否则是 Path Parent。
在 ACCESS(v) 之后,根结点一定是 v 所属的 Auxiliary Tree 的最小结点,我们先把 v 旋转到它所属 的 Auxiliary Tree 的根。再从 v 开始, 沿着 Auxiliary Tree 向左走,直到不能再向左,这个点就是我们要找的根结点。由于使用的是 Splay Tree 数据结构保存 Auxiliary Tree,我们还需要对根结点进行 Splay 操作。
在访问过 v 之后,v 与树的根就在同一个链上了,在 Splay 中就是属于同一棵平衡树。这样 v 所在的平衡树的最左结点就是链的头结点即根结点。
对根结点伸展是为了保证 Splay 的平衡。其实不做也没关系?
先访问 v,然后把 v 旋转到它所属的 Auxiliary Tree 的根,然后再断开 v 在它的所属 Auxiliary Tree 中 与它的左子树的连接,并设置。
显然,v 到根的路径属于同一条链,保存在 Splay 上的同一棵平衡树中,此时 v 为平衡树的根,左子树在链的上方,右子树在链的下方,断开左子树就是断开逻辑树上的父边。
先访问 v,然后修改 v 所属的 Auxiliary Tree 的 Path Parent 为 w,然后再次访问 v。
实际上似乎有一种方法可以直接合并两棵无根树?具体写法见模板。
大致思路是,对于要连接的两点 v、w,先访问 v,再访问 w,之后伸展 v 到根,此时 v 只有左子树没有右子树,因为 v 是剖分出的链的最底端,然后给 v 打一个延迟翻转标记,设他的父亲为 w。之后在每次伸展操作之前,找到要伸展的点的所有的父结点,然后从上到下维护翻转操作。
这样做的原理是,访问 v 剖分出 v 到根的路径,然后将这个路径翻转,也就是树的其它结构不变,而这个把 v 提升到路径的最顶端,而原来的根成为了路径的最底短。
SPOJ OTOCI
3种操作,将不属于同一棵树的两点间建一条边,查询两点路径上的点权和,修改某点的权值。
1 #include <iostream> 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 6 using namespace std; 7 8 const int MaxNode=31000; 9 10 int Lch[MaxNode]; 11 int Rch[MaxNode]; 12 int Pnt[MaxNode]; 13 int Data[MaxNode]; 14 int Sum[MaxNode]; 15 int Rev[MaxNode]; 16 int List[MaxNode]; 17 int Total; 18 19 inline bool isRoot(int t){ 20 return (!Pnt[t]||(Lch[Pnt[t]]!=t&&Rch[Pnt[t]]!=t)); 21 } 22 inline void Update(int cur){ 23 Sum[cur]=Sum[Lch[cur]]+Sum[Rch[cur]]+Data[cur]; 24 } 25 void Reverse(int cur){ 26 if (!Rev[cur]) return; 27 swap(Lch[cur],Rch[cur]); 28 Rev[Lch[cur]]^=1; 29 Rev[Rch[cur]]^=1; 30 Rev[cur]=0; 31 } 32 void LeftRotate(int cur){ 33 if (isRoot(cur)) return; 34 int pnt=Pnt[cur],anc=Pnt[pnt]; 35 Lch[pnt]=Rch[cur]; 36 if (Rch[cur]) Pnt[Rch[cur]]=pnt; 37 Rch[cur]=pnt; 38 Pnt[pnt]=cur; 39 Pnt[cur]=anc; 40 if (anc){ 41 if (Lch[anc]==pnt) Lch[anc]=cur; 42 else if (Rch[anc]==pnt) Rch[anc]=cur; 43 } 44 Update(pnt); 45 Update(cur); 46 } 47 void RightRotate(int cur){ 48 if (isRoot(cur)) return; 49 int pnt=Pnt[cur],anc=Pnt[pnt]; 50 Rch[pnt]=Lch[cur]; 51 if (Lch[cur]) Pnt[Lch[cur]]=pnt; 52 Lch[cur]=pnt; 53 Pnt[pnt]=cur; 54 Pnt[cur]=anc; 55 if (anc){ 56 if (Rch[anc]==pnt) Rch[anc]=cur; 57 else if (Lch[anc]==pnt) Lch[anc]=cur; 58 } 59 Update(pnt); 60 Update(cur); 61 } 62 void Splay(int cur){ 63 int pnt,anc; 64 List[++Total]=cur; 65 for (int i=cur;!isRoot(i);i=Pnt[i]) List[++Total]=Pnt[i]; 66 for (;Total;--Total) 67 if (Rev[List[Total]]) Reverse(List[Total]); 68 while (!isRoot(cur)){ 69 pnt=Pnt[cur]; 70 if (isRoot(pnt)){// 父亲是根结点,做一次旋转 71 if (Lch[pnt]==cur) LeftRotate(cur); 72 else RightRotate(cur); 73 } 74 else{ 75 anc=Pnt[pnt]; 76 if (Lch[anc]==pnt){ 77 if (Lch[pnt]==cur) LeftRotate(pnt),LeftRotate(cur);// 一条线 78 else RightRotate(cur),LeftRotate(cur);// 相反两次 79 } 80 else{ 81 if (Rch[pnt]==cur) RightRotate(pnt),RightRotate(cur);// 一条线 82 else LeftRotate(cur),RightRotate(cur);// 相反两次 83 } 84 } 85 } 86 } 87 int Expose(int u){ 88 int v=0; 89 for (;u;u=Pnt[u]) Splay(u),Rch[u]=v,v=u,Update(u); 90 for (;Lch[v];v=Lch[v]); 91 return v; 92 } 93 void Modify(int x,int d){ 94 Splay(x); 95 Data[x]=d; 96 Update(x); 97 } 98 int Query(int x,int y){ 99 int rx=Expose(x),ry=Expose(y); 100 if (rx==ry){ 101 for (int u=x,v=0;u;u=Pnt[u]){ 102 Splay(u); 103 if (!Pnt[u]) return Sum[Rch[u]]+Data[u]+Sum[v]; 104 Rch[u]=v; 105 Update(u); 106 v=u; 107 } 108 } 109 return -1; 110 } 111 bool Join(int x,int y){ 112 int rx=Expose(x),ry=Expose(y); 113 if (rx==ry) return false; 114 else{ 115 Splay(x); 116 Rch[x]=0; 117 Rev[x]=1; 118 Pnt[x]=y; 119 Update(x); 120 return true; 121 } 122 } 123 void Cut(int x){ 124 if (Pnt[x]){ 125 Expose(x); 126 Pnt[Lch[x]]=0; 127 Lch[x]=0; 128 Update(x); 129 } 130 } 131 int n,Q; 132 133 void init(){ 134 Total=0; 135 memset(Rev,0,sizeof(Rev)); 136 memset(Pnt,0,sizeof(Pnt)); 137 memset(Lch,0,sizeof(Lch)); 138 memset(Rch,0,sizeof(Rch)); 139 memset(Sum,0,sizeof(Sum)); 140 } 141 char cmd[22]; 142 int main() 143 { 144 init(); 145 scanf("%d",&n); 146 for (int i=1;i<=n;i++) scanf("%d",&Data[i]); 147 scanf("%d",&Q); 148 while (Q--){ 149 int x,y; 150 scanf("%s%d%d",cmd,&x,&y); 151 if (cmd[0]=='p'){ 152 Modify(x,y); 153 } 154 if (cmd[0]=='b'){ 155 if (Join(x,y)) printf("yes\n"); 156 else printf("no\n"); 157 } 158 if (cmd[0]=='e'){ 159 int ans=Query(x,y); 160 if (ans==-1) printf("impossible\n"); 161 else printf("%d\n",ans); 162 } 163 } 164 return 0; 165 }
SPOJ QTREE
一棵树,两种操作,询问路径上的边权最大值,修改边权。
由于LCT常数太大,我实在是搞不定这题。
kuangbin巨巨的代码能卡着过去,在这里贴一下。
http://www.cnblogs.com/kuangbin/p/3300217.html
1 /* *********************************************** 2 Author :kuangbin 3 Created Time :2013-9-3 21:06:05 4 File Name :F:\2013ACM练习\专题学习\动态树-LCT\SPOJQTREE.cpp 5 ************************************************ */ 6 7 #include <stdio.h> 8 #include <string.h> 9 #include <iostream> 10 #include <algorithm> 11 #include <vector> 12 #include <queue> 13 #include <set> 14 #include <map> 15 #include <string> 16 #include <math.h> 17 #include <stdlib.h> 18 #include <time.h> 19 using namespace std; 20 21 //对一颗树,进行两个操作: 22 //1.修改边权 23 //2.查询u->v路径上边权的最大值 24 const int MAXN = 10010; 25 int ch[MAXN][2],pre[MAXN]; 26 int Max[MAXN],key[MAXN]; 27 bool rt[MAXN]; 28 void push_down(int r) 29 { 30 31 } 32 void push_up(int r) 33 { 34 Max[r] = max(max(Max[ch[r][0]],Max[ch[r][1]]),key[r]); 35 } 36 void Rotate(int x) 37 { 38 int y = pre[x], kind = ch[y][1]==x; 39 ch[y][kind] = ch[x][!kind]; 40 pre[ch[y][kind]] = y; 41 pre[x] = pre[y]; 42 pre[y] = x; 43 ch[x][!kind] = y; 44 if(rt[y]) 45 rt[y] = false, rt[x] = true; 46 else 47 ch[pre[x]][ch[pre[x]][1]==y] = x; 48 push_up(y); 49 } 50 void P(int r) 51 { 52 if(!rt[r])P(pre[r]); 53 push_down(r); 54 } 55 void Splay(int r) 56 { 57 //P(r); 58 while( !rt[r] ) 59 { 60 int f = pre[r], ff = pre[f]; 61 if(rt[f]) 62 Rotate(r); 63 else if( (ch[ff][1]==f)==(ch[f][1]==r) ) 64 Rotate(f), Rotate(r); 65 else 66 Rotate(r), Rotate(r); 67 } 68 push_up(r); 69 } 70 int Access(int x) 71 { 72 int y = 0; 73 do 74 { 75 Splay(x); 76 rt[ch[x][1]] = true, rt[ch[x][1]=y] = false; 77 push_up(x); 78 x = pre[y=x]; 79 } 80 while(x); 81 return y; 82 } 83 //调用后u是原来u和v的lca,v和ch[u][1]分别存着lca的2个儿子 84 //(原来u和v所在的2颗子树) 85 void lca(int &u,int &v) 86 { 87 Access(v), v = 0; 88 while(u) 89 { 90 Splay(u); 91 if(!pre[u])return; 92 rt[ch[u][1]] = true; 93 rt[ch[u][1]=v] = false; 94 push_up(u); 95 u = pre[v = u]; 96 } 97 } 98 99 void change(int u,int k) 100 { 101 Access(u); 102 key[u] = k; 103 push_up(u); 104 } 105 void query(int u,int v) 106 { 107 lca(u,v); 108 printf("%d\n",max(Max[v],Max[ch[u][1]])); 109 } 110 111 struct Edge 112 { 113 int to,next; 114 int val; 115 int index; 116 }edge[MAXN*2]; 117 int head[MAXN],tot; 118 int id[MAXN]; 119 120 void addedge(int u,int v,int val,int index) 121 { 122 edge[tot].to = v; 123 edge[tot].next = head[u]; 124 edge[tot].val = val; 125 edge[tot].index = index; 126 head[u] = tot++; 127 } 128 void dfs(int u) 129 { 130 for(int i = head[u];i != -1;i = edge[i].next) 131 { 132 int v = edge[i].to; 133 if(pre[v] != 0)continue; 134 pre[v] = u; 135 id[edge[i].index] = v; 136 key[v] = edge[i].val; 137 dfs(v); 138 } 139 } 140 void init() 141 { 142 tot = 0; 143 memset(head,-1,sizeof(head)); 144 } 145 int main() 146 { 147 //freopen("in.txt","r",stdin); 148 //freopen("out.txt","w",stdout); 149 int T; 150 int n; 151 int u,v,w; 152 char op[20]; 153 scanf("%d",&T); 154 while(T--) 155 { 156 init(); 157 scanf("%d",&n); 158 for(int i = 0;i <= n;i++) 159 { 160 pre[i] = 0; 161 ch[i][0] = ch[i][1] = 0; 162 rt[i] = true; 163 } 164 Max[0] = -2000000000; 165 for(int i = 1;i < n;i++) 166 { 167 scanf("%d%d%d",&u,&v,&w); 168 addedge(u,v,w,i); 169 addedge(v,u,w,i); 170 } 171 pre[1] = -1; 172 dfs(1); 173 pre[1] = 0; 174 while(scanf("%s",&op) == 1) 175 { 176 if(op[0] == 'D')break; 177 scanf("%d%d",&u,&v); 178 if(op[0] == 'C') 179 change(id[u],v); 180 else query(u,v); 181 } 182 } 183 return 0; 184 }