首页 > 代码库 > 树的路径剖分算法【强大图解】

树的路径剖分算法【强大图解】

树的路径剖分算法

一.算法简介

树链剖分,计算机术语,指一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每个点属于且只属于一条链,然后再通过数据结构(树状数组、SBT、SPLAY、线段树等)来维护每一条链。

首先引入一道例题:spoj375 QTREE

QTREE - Query on a tree

 

You are given a tree (an acyclic undirected connected graph) with N nodes, and edges numbered 1, 2, 3...N-1.

We will ask you to perfrom some instructions of the following form:

  • CHANGE i ti : change the cost of the i-th edge to ti
    or
  • QUERY a b : ask for the maximum edge cost on the path from node a to node b

Input

The first line of input contains an integer t, the number of test cases (t <= 20). t test cases follow.

For each test case:

  • In the first line there is an integer N (N <= 10000),
  • In the next N-1 lines, the i-th line describes the i-th edge: a line with three integers a b c denotes an edge between a, b of cost c (c <= 1000000),
  • The next lines contain instructions "CHANGE i ti" or "QUERY a b",
  • The end of each test case is signified by the string "DONE".

There is one blank line between successive tests.

Output

For each "QUERY" operation, write one integer representing its result.

Example

Input:131 2 12 3 2QUERY 1 2CHANGE 1 3QUERY 1 2DONEOutput:13

题目大意,给定一个节点数为N的树,支持以下两种操作

1).查询两个节点间路径上权值最大的边

2).修改某条边的权值。

 

对于模拟而言,本题数据较大会超时。本文介绍一种神奇的方法——树的路径剖分算法

 

我们定义:

Size(U): 以该点为根的子树的节点数

Start(U):该点所在链的第一个节点

Pre(U):节点U的父亲节点

DFN(U):在DFS中该节点被搜索的次序

hv(U):节点U的重儿子

 

重儿子:Size较大的儿子

重边:父节点与重儿子的连边

重链:重边组成的一条链

 

对于每个节点,都要将他划分到一条链中,为了保证算法的高效,必须使这条链尽可能长。

一般使用启发式剖分,将Size较大的连在一条链,这条链是重链。

最终按照DFS序加入一个数组中,用线段树等数据结构维护查询即可。

性质 1: 如果 (U,V) 为轻边,则 Size(V) <= Size(U)/2

性质 2: 从根到某一点的路径上轻边的个数不大于 O(log N)

性质 3: 不超过O(logN)条重路径。

 

技术分享

技术分享

通过以上操作,我们将一棵树转化为若干条链。

以上的操作可以通过两遍DFS实现:

     DFS_1:求出每个节点Size[],Pre[]

     DFS_2:从根节点开始,对每个节点的重儿子优先DFS。通过这个操作,可以将在最大的子树中找到一条链。

               对于其他节点而言,都以该点为根节点DFS,最终可以计算得到每条链的链头Start(U), DFN(U) .

二.算法图示

在下图中

Size[U]:表示节点U的子树节点个数

Start[U]:树链的链头

节点旁的红色数字是DFS序

 技术分享

技术分享

技术分享

技术分享

技术分享

技术分享

技术分享

通过上述操作,可以将一颗树剖分成若干条链,再按照DFS序加入一个数组。此时,这棵树已经被转化为一个线性的区间问题,可以使用线段树,Splay Tree 等高效的数据结构解决这个问题。

技术分享

 [ATTENTION]:树链剖分的目的是将高效的数据结构扩展到树上。

三.算法模板&注释:

核心代码如下:

QUERY ( x , y ) // 伪代码         while start[ x ] != start[ y ]                 if DFN[ start[ x ] ] > DFN[ start[ x ] ]                         then Query_min ( DFN[ start[ x ] ] , DFN[ x ] )  , x = pre[ x ]                 else    Query_min ( DFN[ start[ y ] ] , DFN[ y ] )  , y = pre[ y ]         Query_min ( DFN[ x ] , DFN[ y ] )  

 

  1 inline void Push_up ( const int i ) {tr[ i ].mintr = gmax(tr[ i<<1|1 ].mintr , tr[ i<<1 ].mintr );}  2   3 int INPUT ( ) {//读入优化   4         int x=0,f=1;char ch = getchar ( );  5         while ( ch<0||ch>9){if(ch==-)f=-1;ch=getchar() ;}  6         while ( ch >=0&&ch<=9 ) {x = x*10+ch-0;ch = getchar( ) ;}  7         return x*f;  8 }  9  10 void Build_Tree ( int x , int y , int i ) {//建树  11          tr[ i ] . l = x ; tr[ i ] . r = y ; 12          if ( x==y && x==Root ) {tr[ i ].mintr = INF ;return;} 13          if ( x==y ){tr[ i ] . mintr = E_val[ rank[ x ] ] ;return ;} 14          else { 15                  int mid = ( tr[ i ].l + tr[ i ].r ) >> 1 ; 16                  Build_Tree ( x , mid , i<<1 ) ; 17                  Build_Tree ( mid+1 , y , i<<1|1 ) ; 18                  Push_up ( i ) ; 19          } 20 } 21 void Add_Edge ( const int _x , const int _y , const int _val ) {//建图  22          e[ ++cnt ].to = _y ; 23          e[ cnt ].val = _val ; 24          e[ cnt ].next = head[ _x ] ; 25          head[ _x ] = cnt ; 26 } 27  28 int Init_DFS ( const int x , const int father ) {//启发式树链剖分  29          int cnt_ = 1 , max_ = 0 ; 30          for ( int i=head[ x ] ; i ; i=e[ i ].next ) { 31                   int temp = e[ i ].to ; 32                   if ( temp==father ) continue ; 33                   int _ = Init_DFS ( temp , x ) ; 34                  if ( _ > max_ ) {max_ = _ ; hv[ x ] = temp ;} //hv该节点的重儿子  35                  cnt_ +=_; 36          } 37          return cnt_ ;//返回节点个数  38 } 39  40 void DFS ( const int x , const int father ) { 41          if ( !start[ x ] ) start[ x ] = start[ father ] ; 42          DFN[ x ] = ++dfs_num ;//节点的DFS序  43          rank[ dfs_num ] = x ; //节点在DFN中的位置  44          if ( hv[ x ] ) DFS ( hv[ x ] , x ) ;//优先DFS重儿子  45          for ( int i=head[ x ] ; i ; i =e[ i ].next ) { 46                   E_val[ e[ i ].to ] = e[ i ] .val ; 47                   if ( e[ i ].to != hv[ x ] && e[i].to != father ) { 48                            int temp = e[ i ].to ; 49                            start[ temp ] = temp ;//链头  50                            pre [ temp ] = x ;// 父节点  51                            DFS ( temp , x ) ; 52                   } 53          } 54 } 55  56 long long Query_Tree ( int q , int w , int i ) {//查询树  57         if ( q <= tr[i].l && w >= tr[i].r ) return tr[i].mintr;  58          else{ 59                     long long mid = (tr[i].l + tr[i].r) >> 1; 60                     if( q > mid ) { 61                             return Query_Tree ( q , w , i << 1 | 1); 62                     } 63                      else if (w <= mid ) { 64                             return Query_Tree ( q , w , i << 1); 65                     } 66                     else  67                     { 68                             return gmax(Query_Tree ( q , w , i << 1) , Query_Tree ( q , w , i << 1 | 1 )); 69                     } 70             } 71 } 72  73 void Min_Path ( int x , int y ) {//找路径上权值最大的边  74         Ans = INF ; 75         int px = x , py = y ; 76         while ( start[ px ] != start[ py ] ) { 77                   if ( DFN[ start[ px ] ] > DFN[ start[ py ] ] ) { 78                           Ans = gmax ( Ans , Query_Tree ( DFN[start[ px ]] , DFN[px] , 1 ) ) ; 79                           px = pre[ start[ px ] ] ; 80                   } 81                   else { 82                            Ans = gmax ( Ans , Query_Tree ( DFN[start[ py ]] , DFN[py] , 1 ) ) ; 83                           py = pre[ start[ py ] ] ; 84                   } 85                    86         } 87         if(px==py)return ; 88         Ans = gmax ( Ans , Query_Tree ( DFN[px] + 1, DFN[py] , 1 )); 89 } 90  91  92 void DEBUG_ ( int n ) { 93          for ( int i=1 ; i<=n ; ++i ) { 94                   printf ("%d:%d %d %d\n",i,DFN[ i ] ,E_val[ i ],rank[ i ] , E_val[ rank[ i ] ]) ; 95          } 96 } 97  98 void Update_Tree ( int p , int val , int i ) { 99         if ( tr[ i ].l == tr[ i ].r && tr[ i ].l == p ) {100                  tr[ i ].mintr = val ;101                  return ;102         }103         else {104                  int mid = ( tr[ i ].l + tr [ i ].r ) >> 1 ;105                  if ( p>mid ) {106                          Update_Tree ( p , val , i<<1|1 ) ;107                 } 108                 else if ( p<=mid ) {109                          Update_Tree ( p , val , i<<1 ) ;110                 }111                 tr[ i ].mintr = gmax( tr[ i<<1|1 ].mintr , tr[ i<<1 ].mintr ) ;112         }113         114 }115 void Init_all ( ) {116         dfs_num = 0 ; cnt = 0 ;117         memset ( ind , 0 , sizeof ( ind ) ) ;118         memset ( hv , 0 , sizeof ( hv ) ) ;119         memset ( start , 0 , sizeof ( start ) ) ;120         memset ( head , 0 , sizeof ( head ) ) ;121         memset ( pre , 0 , sizeof ( pre ) ) ;122         memset ( rank , 0 , sizeof ( rank ) ) ;123         memset ( pre , 0 , sizeof ( pre ) ) ;124 }125 int main ( ) {126          int N , M ;127          char s[ 50 ] ;128          int T = INPUT ( ) ;129          while ( T-- ) {130                   Init_all ( ) ;131                  N = INPUT ( ) ;132                  for ( int i=1 ; i<=N-1 ; ++i ) {133                           int _x , _y ,_val ;134                           _x= INPUT( );_y= INPUT( );_val= INPUT( );//scanf ( "%d%d%d" ,&_x , &_y ,&_val ) ;135                           Node[ i ].x = _x ; Node[ i ].y = _y ;136                           Add_Edge ( _x , _y , _val ) ;137                           ++ ind[ _y ] ;//记录入度 138                  }139                  for ( int i=1 ; i<=N ; ++i ) if ( !ind[ i ] ) {Root = i ; break ;}//入度为0即为根节点 140         141                  Init_DFS ( Root , Root ) ;//第一遍DFS 142                  start[ Root ] = Root ;143                  DFS ( Root , Root ) ;//第二遍DFS 144          145                  Build_Tree ( 1 , dfs_num , 1 ) ;//建树 146                  //DEBUG_ ( N ) ;  147          148                  while ( true ) {149                           scanf ("%s",s) ;150                           if ( s[ 0 ]==D ) break ;151                           else if ( s[ 0 ]==Q){152                                    int u , v ;153                                    u =INPUT ( ) ; v = INPUT ( ) ;//scanf ( "%d%d" , &u , &v ) ;154                                   Min_Path( u , v ) ;155                                     printf ( "%d\n" , Ans ) ;156                         }157                           else if ( s[ 0 ]==C ){158                                    int tmp , _val ;159                                    tmp =INPUT ( ) ; _val = INPUT ( ) ;//scanf ( "%d%d" , &tmp , &_val ) ;160                                    Update_Tree ( DFN[ Node[tmp].y ] , _val , 1 ) ;161                         }162                  }163         }164          return 0 ; 165 }     

 

2016-09-30 10:47:46

 

(完)

树的路径剖分算法【强大图解】