微信扫一扫打赏支持

树链剖分

树链剖分

总结:

树链剖分实质就是将树上的普通节点变成区间的故事,然后用线段树来求解。

1、

其实树链剖分就是把边哈希到线段树上的数据结构。

实现的话很简单,用两个dfs处理数数的信息,重边以及轻边,然后就是一些线段树的操作了。

2、将普通的树变成每个节点是区间的树,用线段树来解决

3、树链剖分中有重儿子和轻儿子,就是为了将树上的普通节点变成区间。

4、有两轮dfs,第一轮确定信息,第二轮来做树的剖链过程

 

详解:

树链剖分是解决在树上进行插点问线,插线问点等一系列树上的问题

假如现在给你一棵树,然后没两条边之间有一条权值,有一些操作,1:x---y之间的最大权值是多少,2:改变x---y之间的权值

当前这样的操作有很多,如果直接用暴力的方法的话肯定不行,那么就要想一个好的方法,我们可以想一下能不能借助线段树解决,能不能想一种方法对树上的边进行编号,然后就变成区间了。那么我们就可以在线段树上进行操作了,树链剖分就是这样的一个算法。

 

当然编号不是简单的随便编号,如果我们进行随便的编号,然后建立一个线段树,如果要更新一个边的权值,是log2(n)的复杂度,而查找的话,我们要枚举x--y的之间的所有的边,假如我们随便以一个点为根节点进行编号,最大的长度是树的直径,这个值本身是比较大的,而在线段树上查找任意一个区间的复杂度也是log2(n),这样查找一次的时间复杂度比直接暴力还要高,所以很明显是不行的。

那么就要想想办法了,我们能不能把x--y之间的一些边一块儿查找,这就是关于树链剖分的重边和轻边,

重边:某个节点x到孩子节点形成的子树中节点数最多的点child之间的边,由定义发现除了叶子节点其他节点只有一条重边

重边是可以放在一块儿更新的,而有

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

所以这样查找的时间复杂度相当于log2(n)


 

 “在一棵树上进行路径的修改、求极值、求和”乍一看只要线段树就能轻松解决,实际上,仅凭线段树是不能搞定它的。我们需要用到一种貌似高级的复杂算法——树链剖分。

    树链,就是树上的路径。剖分,就是把路径分类为重链和轻链。
    记siz[v]表示以v为根的子树的节点数,dep[v]表示v的深度(根深度为1),top[v]表示v所在的重链的顶端节点,fa[v]表示v的父亲,son[v]表示与v在同一重链上的v的儿子节点(姑且称为重儿子),w[v]表示v与其父亲节点的连边(姑且称为v的父边)在线段树中的位置。只要把这些东西求出来,就能用logn的时间完成原问题中的操作。

    重儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子。
    轻儿子:v的其它子节点。
    重边:点v与其重儿子的连边。
    轻边:点v与其轻儿子的连边。
    重链:由重边连成的路径。
    轻链:轻边。

    剖分后的树有如下性质:
    性质1:如果(v,u)为轻边,则siz[u] * 2 < siz[v];
    性质2:从根到某一点的路径上轻链、重链的个数都不大于logn。
   

    算法实现:
    我们可以用两个dfs来求出fa、dep、siz、son、top、w。
    dfs_1:把fa、dep、siz、son求出来,比较简单,略过。
    dfs_2:⒈对于v,当son[v]存在(即v不是叶子节点)时,显然有top[son[v]] = top[v]。线段树中,v的重边应当在v的父边的后面,记w[son[v]] = totw+1,totw表示最后加入的一条边在线段树中的位置。此时,为了使一条重链各边在线段树中连续分布,应当进行dfs_2(son[v]);
           ⒉对于v的各个轻儿子u,显然有top[u] = u,并且w[u] = totw+1,进行dfs_2过程。
           这就求出了top和w。
    将树中各边的权值在线段树中更新,建链和建线段树的过程就完成了。

    修改操作:例如将u到v的路径上每条边的权值都加上某值x。
    一般人需要先求LCA,然后慢慢修改u、v到公共祖先的边。而高手就不需要了。
    记f1 = top[u],f2 = top[v]。
    当f1 <> f2时:不妨设dep[f1] >= dep[f2],那么就更新u到f1的父边的权值(logn),并使u = fa[f1]。
    当f1 = f2时:u与v在同一条重链上,若u与v不是同一点,就更新u到v路径上的边的权值(logn),否则修改完成;
    重复上述过程,直到修改完成。

    求和、求极值操作:类似修改操作,但是不更新边权,而是对其求和、求极值。
    就这样,原问题就解决了。鉴于鄙人语言表达能力有限,咱画图来看看:树链剖分

    如右图所示,较粗的为重边,较细的为轻边。节点编号旁边有个红色点的表明该节点是其所在链的顶端节点。边旁的蓝色数字表示该边在线段树中的位置。图中1-4-9-13-14为一条重链。

    当要修改11到10的路径时。
    第一次迭代:u = 11,v = 10,f1 = 2,f2 = 10。此时dep[f1] < dep[f2],因此修改线段树中的5号点,v = 4, f2 = 1;
    第二次迭代:dep[f1] > dep[f2],修改线段树中10--11号点。u = 2,f1 = 2;
    第三次迭代:dep[f1] > dep[f2],修改线段树中9号点。u = 1,f1 = 1;
    第四次迭代:f1 = f2且u = v,修改结束。

**数据规模大时,递归可能会爆栈,而非递归dfs会很麻烦,所以可将两个dfs改为宽搜+循环。即先宽搜求出fa、dep,然后逆序循环求出siz、son,再顺序循环求出top和w。

 

代码:

树链剖分用一句话概括就是:把一棵树剖分为若干条链,然后利用数据结构(树状数组,SBT,Splay,线段树等等)去维护每一

条链,复杂度为O(logn)

 

那么,树链剖分的第一步当然是对树进行轻重边的划分。

定义size(x)为以x为根的子树节点个数,令v为u的儿子中size值最大的节点,那么(u,v)就是重边,其余边为轻边。

 

当然,关于这个它有两个重要的性质:

(1)轻边(u,v)中,size(v)<=size(u/2)

(2)从根到某一点的路径上,不超过logn条轻边和不超过logn条重路径。

 

当然,剖分过程分为两次dfs,或者bfs也可以。

 

如果是两次dfs,那么第一次dfs就是找重边,也就是记录下所有的重边。

然后第二次dfs就是连接重边形成重链,具体过程就是:以根节点为起点,沿着重边向下拓展,拉成重链,不在当前重链上的节

点,都以该节点为起点向下重新拉一条重链。

 

 

剖分完毕后,每条重链相当于一段区间,然后用数据结构去维护,把所有重链首尾相接,放到数据结构上,然后维护整体。

 

在这里,当然有很多数组,现在我来分别介绍它们的作用:

 

siz[]数组,用来保存以x为根的子树节点个数

top[]数组,用来保存当前节点的所在链的顶端节点

son[]数组,用来保存重儿子

dep[]数组,用来保存当前节点的深度

fa[]数组,用来保存当前节点的父亲

tid[]数组,用来保存树中每个节点剖分后的新编号

rank[]数组,用来保存当前节点在线段树中的位置

 

那么,我们现在可以根据描述给出剖分的代码:

第一次dfs:记录所有的重边

  1. void dfs1(int u,int father,int d)  
  2. {  
  3.     dep[u]=d;  
  4.     fa[u]=father;  
  5.     siz[u]=1;  
  6.     for(int i=head[u];~i;i=next[i])  
  7.     {  
  8.         int v=to[i];  
  9.         if(v!=father)  
  10.         {  
  11.             dfs1(v,u,d+1);  
  12.             siz[u]+=siz[v];  
  13.             if(son[u]==-1||siz[v]>siz[son[u]])  
  14.                 son[u]=v;  
  15.         }  
  16.     }  
  17. }  


第二次dfs:连重边成重链

  1. void dfs2(int u,int tp)  
  2. {  
  3.     top[u]=tp;  
  4.     tid[u]=++tim;  
  5.     rank[tid[u]]=u;  
  6.     if(son[u]==-1) return;  
  7.     dfs2(son[u],tp);  
  8.     for(int i=head[u];~i;i=next[i])  
  9.     {  
  10.         int v=to[i];  
  11.         if(v!=son[u]&&v!=fa[u])  
  12.             dfs2(v,v);  
  13.     }  
  14. }  


当然,由于题目有时候要求边很多,所以最好不要用二维数组表示边,应用邻接表或者链式前向星。

 

 

当然,这里面有一个重要的操作,那就是修改树中边权的值。

 

如何修改u到v的边权的值呢?这里有两种情况:

(1)如果u与v在同一条重链上,那么就直接修改了

(2)如果u与v不在同一条重链上,那么就一边进行修改,一边将u与v往同一条重链上靠,这样就变成了第一种情况了

 

那么现在的关键问题就是如何将u和v往同一条重链上靠?这个问题此处我就省略了。

 

至此,树链剖分原理基本分析完毕!

 

 

例题:

 

模板“:以spoj 375 为例

  1 #include <cstdio>  
  2 #include <cstring>  
  3 #include <vector>  
  4 #include <algorithm>  
  5 using namespace std;  
  6 #define Del(a,b) memset(a,b,sizeof(a))  
  7 const int N = 10005;  
  8   
  9 int dep[N],siz[N],fa[N],id[N],son[N],val[N],top[N]; //top 最近的重链父节点  
 10 int num;  
 11 vector<int> v[N];  
 12 struct tree  
 13 {  
 14     int x,y,val;  
 15     void read(){  
 16         scanf("%d%d%d",&x,&y,&val);  
 17     }  
 18 };  
 19 tree e[N];  
 20 void dfs1(int u, int f, int d) {  
 21     dep[u] = d;  
 22     siz[u] = 1;  
 23     son[u] = 0;  
 24     fa[u] = f;  
 25     for (int i = 0; i < v[u].size(); i++) {  
 26         int ff = v[u][i];  
 27         if (ff == f) continue;  
 28         dfs1(ff, u, d + 1);  
 29         siz[u] += siz[ff];  
 30         if (siz[son[u]] < siz[ff])  
 31             son[u] = ff;  
 32     }  
 33 }  
 34 void dfs2(int u, int tp) {  
 35     top[u] = tp;  
 36     id[u] = ++num;  
 37     if (son[u]) dfs2(son[u], tp);  
 38     for (int i = 0; i < v[u].size(); i++) {  
 39         int ff = v[u][i];  
 40         if (ff == fa[u] || ff == son[u]) continue;  
 41         dfs2(ff, ff);  
 42     }  
 43 }  
 44 #define lson(x) ((x<<1))  
 45 #define rson(x) ((x<<1)+1)  
 46 struct Tree  
 47 {  
 48     int l,r,val;  
 49 };  
 50 Tree tree[4*N];  
 51 void pushup(int x) {  
 52     tree[x].val = max(tree[lson(x)].val, tree[rson(x)].val);  
 53 }  
 54   
 55 void build(int l,int r,int v)  
 56 {  
 57     tree[v].l=l;  
 58     tree[v].r=r;  
 59     if(l==r)  
 60     {  
 61         tree[v].val = val[l];  
 62         return ;  
 63     }  
 64     int mid=(l+r)>>1;  
 65     build(l,mid,v*2);  
 66     build(mid+1,r,v*2+1);  
 67     pushup(v);  
 68 }  
 69 void update(int o,int v,int val)  //log(n)  
 70 {  
 71     if(tree[o].l==tree[o].r)  
 72     {  
 73         tree[o].val = val;  
 74         return ;  
 75     }  
 76     int mid = (tree[o].l+tree[o].r)/2;  
 77     if(v<=mid)  
 78         update(o*2,v,val);  
 79     else  
 80         update(o*2+1,v,val);  
 81     pushup(o);  
 82 }  
 83 int query(int x,int l, int r)  
 84 {  
 85     if (tree[x].l >= l && tree[x].r <= r) {  
 86         return tree[x].val;  
 87     }  
 88     int mid = (tree[x].l + tree[x].r) / 2;  
 89     int ans = 0;  
 90     if (l <= mid) ans = max(ans, query(lson(x),l,r));  
 91     if (r > mid) ans = max(ans, query(rson(x),l,r));  
 92     return ans;  
 93 }  
 94   
 95 int Yougth(int u, int v) {  
 96     int tp1 = top[u], tp2 = top[v];  
 97     int ans = 0;  
 98     while (tp1 != tp2) {  
 99         //printf("YES\n");  
100         if (dep[tp1] < dep[tp2]) {  
101             swap(tp1, tp2);  
102             swap(u, v);  
103         }  
104         ans = max(query(1,id[tp1], id[u]), ans);  
105         u = fa[tp1];  
106         tp1 = top[u];  
107     }  
108     if (u == v) return ans;  
109     if (dep[u] > dep[v]) swap(u, v);  
110     ans = max(query(1,id[son[u]], id[v]), ans);  
111     return ans;  
112 }  
113 void Clear(int n)  
114 {  
115     for(int i=1;i<=n;i++)  
116         v[i].clear();  
117 }  
118 int main()  
119 {  
120     //freopen("Input.txt","r",stdin);  
121     int T;  
122     scanf("%d",&T);  
123     while(T--)  
124     {  
125         int n;  
126         scanf("%d",&n);  
127         for(int i=1;i<n;i++)  
128         {  
129             e[i].read();  
130             v[e[i].x].push_back(e[i].y);  
131             v[e[i].y].push_back(e[i].x);  
132         }  
133         num = 0;  
134         dfs1(1,0,1);  
135         dfs2(1,1);  
136         for (int i = 1; i < n; i++) {  
137             if (dep[e[i].x] < dep[e[i].y]) swap(e[i].x, e[i].y);  
138             val[id[e[i].x]] = e[i].val;  
139         }  
140         build(1,num,1);  
141         char s[200];  
142         while(~scanf("%s",&s) && s[0]!='D')  
143         {  
144             int x,y;  
145             scanf("%d%d",&x,&y);  
146             if(s[0]=='Q')  
147                 printf("%d\n",Yougth(x,y));  
148             if (s[0] == 'C')  
149                 update(1,id[e[x].x],y);  
150         }  
151         Clear(n);  
152     }  
153     return 0;  
154 }  

 

  1 #include <cstdio>
  2 #include <algorithm>
  3 #include <iostream>
  4 #include <string.h>
  5 using namespace std;
  6 const int maxn = 10010;
  7 struct Tedge
  8 { int b, next; } e[maxn * 2];
  9 int tree[maxn];
 10 int zzz, n, z, edge, root, a, b, c;
 11 int d[maxn][3];
 12 int first[maxn], dep[maxn], w[maxn], fa[maxn], top[maxn], son[maxn], siz[maxn];
 13 char ch[10];
 14 
 15 void insert(int a, int b, int c)
 16 {
 17      e[++edge].b = b;
 18      e[edge].next = first[a];
 19      first[a] = edge;
 20 }
 21 
 22 void dfs(int v)
 23 {
 24      siz[v] = 1; son[v] = 0;
 25      for (int i = first[v]; i > 0; i = e[i].next)
 26          if (e[i].b != fa[v])
 27          {
 28              fa[e[i].b] = v;
 29              dep[e[i].b] = dep[v]+1;
 30              dfs(e[i].b);
 31              if (siz[e[i].b] > siz[son[v]]) son[v] = e[i].b;
 32              siz[v] += siz[e[i].b];
 33          }
 34 }
 35 
 36 void build_tree(int v, int tp)
 37 {
 38      w[v] = ++ z; top[v] = tp;
 39      if (son[v] != 0) build_tree(son[v], top[v]);
 40      for (int i = first[v]; i > 0; i = e[i].next)
 41          if (e[i].b != son[v] && e[i].b != fa[v])
 42              build_tree(e[i].b, e[i].b);
 43 }
 44 
 45 void update(int root, int lo, int hi, int loc, int x)
 46 {
 47      if (loc > hi || lo > loc) return;
 48      if (lo == hi)
 49      { tree[root] = x; return; }
 50      int mid = (lo + hi) / 2, ls = root * 2, rs = ls + 1;
 51      update(ls, lo, mid, loc, x);
 52      update(rs, mid+1, hi, loc, x);
 53      tree[root] = max(tree[ls], tree[rs]);
 54 }
 55 
 56 int maxi(int root, int lo, int hi, int l, int r)
 57 {
 58      if (l > hi || r < lo) return 0;
 59      if (l <= lo && hi <= r) return tree[root];
 60      int mid = (lo + hi) / 2, ls = root * 2, rs = ls + 1;
 61      return max(maxi(ls, lo, mid, l, r), maxi(rs, mid+1, hi, l, r));
 62 }
 63 
 64 inline int find(int va, int vb)
 65 {
 66      int f1 = top[va], f2 = top[vb], tmp = 0;
 67      while (f1 != f2)
 68      {
 69            if (dep[f1] < dep[f2])
 70            { swap(f1, f2); swap(va, vb); }
 71            tmp = max(tmp, maxi(1, 1, z, w[f1], w[va]));
 72            va = fa[f1]; f1 = top[va];
 73      }
 74      if (va == vb) return tmp;
 75      if (dep[va] > dep[vb]) swap(va, vb);
 76      return max(tmp, maxi(1, 1, z, w[son[va]], w[vb]));  //
 77 }
 78 
 79 void init()
 80 {
 81      scanf("%d", &n);
 82      root = (n + 1) / 2;
 83      fa[root] = z = dep[root] = edge = 0;
 84      memset(siz, 0, sizeof(siz));
 85      memset(first, 0, sizeof(first));
 86      memset(tree, 0, sizeof(tree));
 87      for (int i = 1; i < n; i++)
 88      {
 89          scanf("%d%d%d", &a, &b, &c);
 90          d[i][0] = a; d[i][1] = b; d[i][2] = c;
 91          insert(a, b, c);
 92          insert(b, a, c);
 93      }
 94      dfs(root);
 95      build_tree(root, root);    //
 96      for (int i = 1; i < n; i++)
 97      {
 98          if (dep[d[i][0]] > dep[d[i][1]]) swap(d[i][0], d[i][1]);
 99          update(1, 1, z, w[d[i][1]], d[i][2]);
100      }
101 }
102 
103 inline void read()
104 {
105      ch[0] = ' ';
106      while (ch[0] < 'C' || ch[0] > 'Q') scanf("%s", &ch);
107 }
108 
109 void work()
110 {
111      for (read(); ch[0] != 'D'; read())
112      {
113          scanf("%d%d", &a, &b);
114          if (ch[0] == 'Q') printf("%d\n", find(a, b));
115                       else update(1, 1, z, w[d[a][1]], b);
116      }
117 }
118 
119 int main()
120 {
121     for (scanf("%d", &zzz); zzz > 0; zzz--)
122     {
123         init();
124         work();
125     }
126     return 0;
127 }

 

posted @ 2018-01-08 18:26  范仁义  阅读(451)  评论(0编辑  收藏  举报