• 树链剖分


    树链剖分

    总结:

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

    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
    ");  
    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
    ",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
    ", 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 }
  • 相关阅读:
    http服务详解(1)——一次完整的http服务请求处理过程
    Mysql数据库的二进制安装和基础入门操作
    DNS—正、反向解析;委派;主从;子域;转发;智能dns等的实现
    CA认证和颁发吊销证书
    Maven构建Web项目
    MySQL,查看连接数和状态等
    Maven编译出现“[ERROR] java.lang.OutOfMemoryError: Java heap space”
    Map集合
    List集合
    Myecplise反编译工具安装
  • 原文地址:https://www.cnblogs.com/Renyi-Fan/p/8244137.html
Copyright © 2020-2023  润新知