cyendra

Azarath Metrion Zinthos

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

什么是树链剖分

树链剖分并不是一个复杂的算法或者数据结构,它能把一棵树拆成链。

树链,就是树上的路径。剖分,就是把路径分类为重链和轻链。

给定一棵树,将它划分成若干条互不相交的路径,满足:从节点 u->v 最多经过 logn 条路径以及 logn 条不在路径上的边。

树链剖分后,我们就可以利用其它的数据结构对在一棵树上进行路径的修改、求极值、求和的一类问题进行求解了。

 

一些定义

重儿子:以结点的一个儿子为根的子树的结点个数中最大的那一个儿子称为重儿子。

轻儿子:除了重儿子以外的儿子。

重边:结点与其重儿子的边称为重边。

轻边:结点与其轻儿子的边称为轻边。

重链:由重边组成的路径。

轻链:由轻边组成的路径。

 

描述剖分后的树

描述结点:

siz[v]:以结点v为根的子树的结点个数

dep[v]:结点v的深度,定义根结点的深度为0

fa[v]:结点v的父亲结点

 

描述结点与路径之间的关系:

belong[v]:结点v所属的路径编号

idx[v]:结点v在其路径中的编号

son[v]:结点v的重儿子

 

描述路径:

top[p]:编号为p的路径的顶端结点

len[p]:路径p的长度

 

描述辅助数据结构:

sump[p]:路径p的编号

seg[v]:结点v的父边在线段树中的位置

wei[v]:结点v的父边的权值

 

剖分后的性质

如果 (u,v) 为轻边,则 siz[v] * 2 < siz[u],即以轻儿子为根的子树中的结点数量相对少。

从根到某一点的路径上轻链、重链的个数都不大于 logn。

 

树链剖分的实现

首先用一次搜索求出 dep、fa、wei 的值,深搜广搜都可以,但是深搜容易爆栈,这里使用广搜。

在广搜的过程中得到了树的拓扑序,我们按照拓扑序的逆序遍历所有的结点。

在这个过程中可以求出 siz 与 son。

对于一个结点,如果它是叶子结点,那么我们就新建一条链,使该结点作为链上的第一个结点;

如果不是叶子结点,那么它与它的重儿子属于同一条链,对链进行更新即可。

 1 void split(){
 2     memset(dep,-1,sizeof(dep));
 3     l=0;
 4     dep[ que[r=1]=1 ]=0; // 将根结点插入队列,并设深度为0
 5     fa[1]=-1; // 默认 1 为根结点
 6     wei[1]=0;
 7     while (l<r){ // 第一遍搜索求出 fa,dep,wei
 8         int u=que[++l];
 9         vis[u]=false; // 顺便初始化vis
10         for (int i=head[u];i!=-1;i=edges[i].next){
11             int v=edges[i].to;
12             int w=edges[i].w;
13             if (dep[v]==-1){ // 未访问过的结点
14                 dep[ que[++r]=v ]=dep[u]+1; // 将v插入队列并设深度为dep[u]+1
15                 fa[v]=u; // v的父结点为u
16                 wei[v]=w; // v的父边权值
17             }
18         }
19     }
20     cnt=0; // 重链编号
21     for (int i=n;i>0;i--){
22         int u=que[i],p=-1;
23         siz[u]=1;
24         son[u]=p;
25         for (int k=head[u];k!=-1;k=edges[k].next){
26             int v=edges[k].to;
27             if (vis[v]){ // 若v是u的子结点
28                 siz[u]+=siz[v]; // 计数
29                 if (p==-1||siz[v]>siz[p]){
30                     son[u]=v;
31                     p=v; // u的重儿子是v
32                 }
33             }
34         }
35         if (p==-1){ // u是叶子结点
36             idx[u]=len[++cnt]=1; // 一个新的路径编号为cnt,u是路径中的第一个结点
37             belong[ top[cnt]=u ]=cnt; // u是顶端结点,且u属于路径cnt
38         }
39         else{ // u不是叶子结点
40             idx[u]=++len[ belong[u]=belong[p] ]; // u属于重儿子所在的链,链长+1,u是路径中第len个结点
41             top[ belong[u] ]=u; // u是顶端结点
42         }
43         vis[u]=true; // 访问标记
44     }
45 }
树链剖分

 

路径上的求和、求极值操作

我们对树上的边进行编号,编号对应着该边在线段树上的位置,保证同一个重链上的所有点的父边的编号是连续的即可。

这样我们就可以通过对线段树进行区间操作来快速的修改同一条重链上的权值了。

如图所示:

如何快速的处理任意两个结点 (va,vb) 间的边呢。

我们设 f1=top[belong[va]],f2=top[belong[vb]]。表示 f1 是 va 所属的链上的最顶端的点, f2 是 vb 所属的链上的最顶端的点。

当 f1 != f2 时,若 dep[f1] >= dep[f2],那么就处理 va 的父边到 f1 父边的路径,由于它们属于同一条重链的父边,编号是连续的,因此可以用线段树进行区间处理,最后使 va = fa[f1],重复进行该步骤直到 f1=f2。

当 f1 = f2 时,va 与 vb 在同一条重链上,若 va 与 vb 不是同一点,就区间处理 va 到 vb 路径上的边,否则修改完成。

查询操作与修改操作是类似的。

例如要查找两个结点间路径上的权值最大的边:

 1 int find(int va,int vb){
 2     int f1=top[belong[va]],f2=top[belong[vb]],tmp=0;
 3     while (f1!=f2){
 4         if (dep[f1]<dep[f2]){
 5             swap(f1,f2);
 6             swap(va,vb);
 7         }
 8         tmp=max(tmp,tr.query(1,seg[f1],seg[va]));
 9         va=fa[f1];
10         f1=top[belong[va]];
11     }
12     if (va==vb) return tmp;
13     if (dep[va]>dep[vb]) swap(va,vb);
14     return max(tmp,tr.query(1,seg[son[va]],seg[vb]));
15 }
查找路径上边权最大的边

 

相关练习题

SPOJ 375. Query on a tree

题目给出一棵含有n个结点的树,n-1条边每条边都有边权,有两种操作,修改第i条边的边权,查询两个结点的路径上最大的边权。

首先进行树链剖分,对边进行编号,建立线段树储存权值。

对于修改操作,我们找到该边在线段树上的位置然后单点更新即可。

对于查询操作,利用重链上的父边编号相同这一性质不断进行区间查询,就能快速找到最大的边权。

 

POJ 3237 Tree

与上一题相比多了一种操作,将两个结点间的边权取负。

用线段树维护两个值,区间上的最小值与最大值。区间取负时将最大值最小值取负后交换,打上延迟标记。

  1 #include <iostream>
  2 #include <cstdio>
  3 #include <cstring>
  4 #include <algorithm>
  5 
  6 using namespace std;
  7 
  8 const int maxn=101010+5;
  9 const int maxm=maxn+maxn;
 10 
 11 struct EDGENODE{
 12     int to;
 13     int w;
 14     int next;
 15 }edges[maxm];
 16 int head[maxn],edge;
 17 inline void init(){
 18     edge=0;
 19     memset(head,-1,sizeof(head));
 20 }
 21 inline void addedge(int u,int v,int w){
 22     edges[edge].w=w,edges[edge].to=v,edges[edge].next=head[u],head[u]=edge++;
 23     edges[edge].w=w,edges[edge].to=u,edges[edge].next=head[v],head[v]=edge++;
 24 }
 25 int que[maxn]; // 队列
 26 bool vis[maxn]; // 访问标记
 27 int son[maxn]; // 重儿子
 28 int idx[maxn]; // 结点v在其路径中的编号
 29 int dep[maxn]; // 结点v的深度
 30 int siz[maxn]; // 以结点v为根的子树的结点个数
 31 int belong[maxn]; // 结点v所属的路径编号
 32 int fa[maxn]; // 结点v的父亲结点
 33 int top[maxn]; // 编号为p的路径的顶端结点
 34 int len[maxn]; // 路径p的长度
 35 int sump[maxn]; // 路径p的编号
 36 int seg[maxn]; // 结点v的父边在线段树中的位置
 37 int wei[maxn]; // 结点v的父边的权值
 38 int l,r,ans,cnt;
 39 int n;
 40 char cmd[22];
 41 
 42 void split(){
 43     memset(dep,-1,sizeof(dep));
 44     l=0;
 45     dep[ que[r=1]=1 ]=0; // 将根结点插入队列,并设深度为0
 46     fa[1]=-1; // 默认 1 为根结点
 47     wei[1]=0;
 48     while (l<r){ // 第一遍搜索求出 fa,dep,wei
 49         int u=que[++l];
 50         vis[u]=false; // 顺便初始化vis
 51         for (int i=head[u];i!=-1;i=edges[i].next){
 52             int v=edges[i].to;
 53             int w=edges[i].w;
 54             if (dep[v]==-1){ // 未访问过的结点
 55                 dep[ que[++r]=v ]=dep[u]+1; // 将v插入队列并设深度为dep[u]+1
 56                 fa[v]=u; // v的父结点为u
 57                 wei[v]=w; // v的父边权值
 58             }
 59         }
 60     }
 61     cnt=0; // 重链编号
 62     for (int i=n;i>0;i--){
 63         int u=que[i],p=-1;
 64         siz[u]=1;
 65         son[u]=p;
 66         for (int k=head[u];k!=-1;k=edges[k].next){
 67             int v=edges[k].to;
 68             if (vis[v]){ // 若v是u的子结点
 69                 siz[u]+=siz[v]; // 计数
 70                 if (p==-1||siz[v]>siz[p]){
 71                     son[u]=v;
 72                     p=v; // u的重儿子是v
 73                 }
 74             }
 75         }
 76         if (p==-1){ // u是叶子结点
 77             idx[u]=len[++cnt]=1; // 一个新的路径编号为cnt,u是路径中的第一个结点
 78             belong[ top[cnt]=u ]=cnt; // u是顶端结点,且u属于路径cnt
 79         }
 80         else{ // u不是叶子结点
 81             idx[u]=++len[ belong[u]=belong[p] ]; // u属于重儿子所在的链,链长+1,u是路径中第len个结点
 82             top[ belong[u] ]=u; // u是顶端结点
 83         }
 84         vis[u]=true; // 访问标记
 85     }
 86 }
 87 const int INF=0x3fffffff;
 88 struct SegmentTree{
 89     int num[maxn];
 90     struct Tree{
 91         int l;
 92         int r;
 93         int max;
 94         int min;
 95         bool neg;
 96     };
 97     Tree tree[maxn*4];
 98     void push_down(int root){
 99         if (tree[root].neg){
100             if (tree[root].l!=tree[root].r){
101                 tree[root<<1].neg^=1;
102                 tree[root<<1|1].neg^=1;
103                 swap(tree[root<<1].max,tree[root<<1].min);
104                 swap(tree[root<<1|1].max,tree[root<<1|1].min);
105                 tree[root<<1].max*=-1;
106                 tree[root<<1].min*=-1;
107                 tree[root<<1|1].max*=-1;
108                 tree[root<<1|1].min*=-1;
109             }
110         }
111         tree[root].neg=0;
112     }
113     void push_up(int root){
114         tree[root].max=max(tree[root<<1].max,tree[root<<1|1].max);
115         tree[root].min=min(tree[root<<1].min,tree[root<<1|1].min);
116     }
117     void build(int root,int l,int r){
118         tree[root].l=l;
119         tree[root].r=r;
120         tree[root].neg=0;
121         if(tree[root].l==tree[root].r){
122             tree[root].max=num[l];
123             tree[root].min=num[l];
124             tree[root].neg=0;
125             return;
126         }
127         int mid=(l+r)/2;
128         build(root<<1,l,mid);
129         build(root<<1|1,mid+1,r);
130         push_up(root);
131     }
132     void update(int root,int pos,int val){
133         if(tree[root].l==tree[root].r){
134             tree[root].max=val;
135             tree[root].min=val;
136             return;
137         }
138         push_down(root);
139         int mid=(tree[root].l+tree[root].r)/2;
140         if(pos<=mid) update(root<<1,pos,val);
141         else update(root<<1|1,pos,val);
142         push_up(root);
143     }
144     int query(int root,int L,int R){
145         if(L<=tree[root].l&&R>=tree[root].r) return tree[root].max;
146         push_down(root);
147         int mid=(tree[root].l+tree[root].r)/2,ret=-INF;
148         if(L<=mid) ret=max(ret,query(root<<1,L,R));
149         if(R>mid) ret=max(ret,query(root<<1|1,L,R));
150         push_up(root);
151         return ret;
152     }
153     void nega(int root,int L,int R){
154         if (L<=tree[root].l&&R>=tree[root].r){
155             tree[root].neg^=1;
156             swap(tree[root].max,tree[root].min);
157             tree[root].max*=-1;
158             tree[root].min*=-1;
159             return;
160         }
161         push_down(root);
162         int mid=(tree[root].l+tree[root].r)/2;
163         if (L<=mid) nega(root<<1,L,R);
164         if (R>mid) nega(root<<1|1,L,R);
165         push_up(root);
166     }
167     void debug(int root){
168         printf("rt=%d, [%d~%d], min=%d, max=%d, neg=%d\n",root,tree[root].l,tree[root].r,tree[root].min,tree[root].max,(int)tree[root].neg);
169         if (tree[root].l==tree[root].r) return;
170         debug(root<<1);
171         debug(root<<1|1);
172     }
173 }tr;
174 
175 int find(int va,int vb){
176     int f1=top[belong[va]],f2=top[belong[vb]],tmp=-INF;
177     while (f1!=f2){
178         if (dep[f1]<dep[f2]){
179             swap(f1,f2);
180             swap(va,vb);
181         }
182         tmp=max(tmp,tr.query(1,seg[f1],seg[va]));
183         va=fa[f1];
184         f1=top[belong[va]];
185     }
186     if (va==vb) return tmp;
187     if (dep[va]>dep[vb]) swap(va,vb);
188     return max(tmp,tr.query(1,seg[son[va]],seg[vb]));
189 }
190 void gao(int va,int vb){
191     int f1=top[belong[va]],f2=top[belong[vb]];
192     while (f1!=f2){
193         if (dep[f1]<dep[f2]){
194             swap(f1,f2);
195             swap(va,vb);
196         }
197         tr.nega(1,seg[f1],seg[va]);
198         va=fa[f1];
199         f1=top[belong[va]];
200     }
201     if (va==vb) return;
202     if (dep[va]>dep[vb]) swap(va,vb);
203     tr.nega(1,seg[son[va]],seg[vb]);
204 }
205 int d[maxn][3];
206 
207 int main()
208 {
209     int T;
210     scanf("%d",&T);
211     while (T--){
212         init();
213         scanf("%d",&n);
214         for (int i=1;i<n;i++){
215             int a,b,c;
216             scanf("%d%d%d",&a,&b,&c);
217             d[i][0]=a;
218             d[i][1]=b;
219             d[i][2]=c;
220             addedge(a,b,c);
221         }
222         split();
223         sump[0]=0;
224         for (int i=1;i<=cnt;i++) sump[i]=sump[i-1]+len[i];
225         for (int i=1;i<=n;i++){
226             seg[i]=sump[ belong[i] ]-idx[i]+1;
227             tr.num[ seg[i] ]=wei[i];
228         }
229         tr.build(1,1,n);
230         while (scanf("%s",cmd)){
231             if (cmd[0]=='D') break;
232             int x,y;
233             scanf("%d%d",&x,&y);
234             if (cmd[0]=='Q'){
235                 printf("%d\n",find(x,y));
236             }
237             if (cmd[0]=='C'){
238                 if (fa[d[x][1]]==d[x][0]) tr.update(1,seg[d[x][1]],y);
239                 else tr.update(1,seg[d[x][0]],y);
240             }
241             if (cmd[0]=='N'){
242                 gao(x,y);
243             }
244         }
245     }
246     return 0;
247 }
248 
249 /**
250 1
251 10
252 1 2 1
253 2 3 3
254 3 4 8
255 4 5 -6
256 5 6 -9
257 6 7 -1
258 7 8 1
259 8 9 7
260 9 10 6
261 NEGATE 1 8
262 CHANGE 2 7
263 QUERY 1 3
264 NEGATE 1 5
265 CHANGE 4 7
266 QUERY 3 5
267 DONE
268 
269 3
270 10
271 1 2 1
272 2 3 7
273 3 4 8
274 4 5 6
275 5 6 9
276 6 7 1
277 7 8 1
278 8 9 7
279 9 10 6
280 NEGATE 4 7
281 CHANGE 2 3
282 QUERY 2 9
283 CHANGE 2 3
284 QUERY 6 8
285 NEGATE 1 8
286 CHANGE 2 7
287 QUERY 1 3
288 NEGATE 1 5
289 CHANGE 4 7
290 QUERY 3 5
291 DONE
292 6
293 1 2 1
294 2 3 2
295 3 4 4
296 4 5 100
297 5 6 -1000
298 QUERY 1 2
299 CHANGE 1 3
300 QUERY 1 2
301 NEGATE 1 2
302 QUERY 1 3
303 QUERY 1 2
304 CHANGE 1 10
305 QUERY 1 3
306 NEGATE 1 2
307 QUERY 1 3
308 CHANGE 1 10
309 CHANGE 2 20
310 QUERY 1 3
311 NEGATE 1 2
312 QUERY 1 3
313 NEGATE 2 3
314 QUERY 1 3
315 CHANGE 1 -100
316 CHANGE 2 -1000
317 QUERY 1 4
318 NEGATE 1 6
319 QUERY 1 6
320 DONE
321 100
322 1 2 265
323 2 3 133
324 3 4 508
325 4 5 197
326 5 6 437
327 6 7 849
328 7 8 577
329 8 9 503
330 9 10 478
331 10 11 434
332 11 12 877
333 12 13 691
334 13 14 54
335 14 15 295
336 15 16 421
337 16 17 166
338 17 18 550
339 18 19 410
340 19 20 868
341 20 21 476
342 21 22 283
343 22 23 410
344 23 24 915
345 24 25 308
346 25 26 301
347 26 27 553
348 27 28 609
349 28 29 733
350 29 30 770
351 30 31 635
352 31 32 581
353 32 33 753
354 33 34 707
355 34 35 448
356 35 36 738
357 36 37 841
358 37 38 389
359 38 39 532
360 39 40 210
361 40 41 458
362 41 42 595
363 42 43 989
364 43 44 678
365 44 45 214
366 45 46 746
367 46 47 548
368 47 48 117
369 48 49 758
370 49 50 437
371 50 51 840
372 51 52 555
373 52 53 726
374 53 54 490
375 54 55 719
376 55 56 403
377 56 57 329
378 57 58 92
379 58 59 311
380 59 60 664
381 60 61 207
382 61 62 170
383 62 63 548
384 63 64 713
385 64 65 556
386 65 66 705
387 66 67 82
388 67 68 508
389 68 69 59
390 69 70 45
391 70 71 670
392 71 72 540
393 72 73 826
394 73 74 262
395 74 75 504
396 75 76 989
397 76 77 408
398 77 78 896
399 78 79 388
400 79 80 15
401 80 81 485
402 81 82 219
403 82 83 977
404 83 84 641
405 84 85 985
406 85 86 189
407 86 87 64
408 87 88 641
409 88 89 320
410 89 90 788
411 90 91 441
412 91 92 785
413 92 93 163
414 93 94 153
415 94 95 852
416 95 96 36
417 96 97 10
418 97 98 145
419 98 99 956
420 99 100 641
421 QUERY 32 69
422 NEGATE 1 22
423 CHANGE 40 53
424 QUERY 17 38
425 NEGATE 17 65
426 CHANGE 49 68
427 QUERY 44 52
428 NEGATE 11 53
429 CHANGE 9 68
430 QUERY 2 49
431 NEGATE 25 45
432 CHANGE 23 67
433 QUERY 89 90
434 NEGATE 5 37
435 CHANGE 27 53
436 QUERY 22 86
437 NEGATE 6 7
438 CHANGE 17 23
439 QUERY 78 93
440 NEGATE 30 63
441 CHANGE 56 99
442 QUERY 3 29
443 NEGATE 24 38
444 CHANGE 9 95
445 QUERY 63 66
446 NEGATE 69 92
447 CHANGE 9 91
448 QUERY 7 27
449 NEGATE 32 60
450 CHANGE 48 77
451 QUERY 47 94
452 NEGATE 14 27
453 CHANGE 50 99
454 QUERY 38 97
455 NEGATE 11 67
456 CHANGE 74 83
457 QUERY 28 81
458 NEGATE 13 53
459 CHANGE 55 88
460 QUERY 2 66
461 NEGATE 71 95
462 CHANGE 32 74
463 QUERY 14 50
464 NEGATE 1 28
465 CHANGE 16 80
466 QUERY 36 75
467 NEGATE 20 49
468 CHANGE 22 54
469 QUERY 5 46
470 NEGATE 12 37
471 CHANGE 61 94
472 QUERY 18 92
473 NEGATE 19 26
474 CHANGE 6 94
475 QUERY 33 60
476 NEGATE 79 87
477 CHANGE 30 75
478 QUERY 55 94
479 NEGATE 28 79
480 CHANGE 23 31
481 QUERY 91 95
482 NEGATE 28 76
483 CHANGE 8 41
484 QUERY 6 25
485 NEGATE 19 70
486 CHANGE 17 54
487 QUERY 52 66
488 NEGATE 4 95
489 CHANGE 19 52
490 QUERY 73 87
491 DONE
492 
493 
494 **/
POJ 3237 Tree

 

posted on 2014-08-08 15:41  cyendra  阅读(859)  评论(0编辑  收藏  举报