首页 > 代码库 > 树的路径剖分算法【强大图解】
树的路径剖分算法【强大图解】
树的路径剖分算法
一.算法简介
树链剖分,计算机术语,指一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每个点属于且只属于一条链,然后再通过数据结构(树状数组、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
(完)
树的路径剖分算法【强大图解】