树链剖分学习笔记

树链剖分(tree_decomposition)-轻重链剖分

-还有长链剖分、实链剖分等。

原理(概念)

前置知识:线段树

树剖需要处理的基本问题:

  • 将树从x到y结点“最短路径”上所有节点的值都加上z(树上差分也可以;
  • 求树从x到y结点“最短路径”上所有节点的值之和(LCA也可以);
    具体做法:边点权互化,利用LCA性质,\(dis(x,y)=dis(x)+dis(y)-2*dis(lca(x,y))\)
  • 将以x为根节点的子树内所有节点值都加上z;
  • 求以x为根节点的子树内所有节点值之和。

概念:

树链剖(pou)分指的是把一棵树分成\(logn\)数量级的多条互不相交的链,把树形结构变为线性结构以降低某些操作复杂度的算法思想。
定义size(x)为以树上某一非叶子节点x为根的子树的节点个数(此处“子树”相对于整棵树而言),则:

  • 重儿子:对于树上任一非叶节点x,其子节点中size(x)最大的儿子为其重儿子。

  • 轻儿子:对于树上任一非叶节点x,其子节点中的非重儿子

  • 重边:连接父节点及其重儿子的边;

  • 轻边:树上除去重边的边都为轻边;

  • 重链:由多条重边连接的路径(轻链的定义类似);
    性质:
    0. 剖分后保证每一个点都在且仅在一条链(即所有重链将树完全剖分),链上节点深度递增(DFS序连续);

    1. 落单的节点一般算作单独一条重链;
      即对于叶子节点,若其为轻儿子,则有一条以自己为起点的长度为\(1\)的重链
    2. 每一条重链以轻儿子/根为起点;
    3. 如果\((u,v)\)是一条轻边,那么\(size(v)<size(u)/2\)
    4. 从根结点到任意结点的路所经过的轻重链的个数必定都小于等于\(logn\)(完全二叉树可以卡满)(证明:每遇到一条轻边,子树大小就会减半,详见oiwiki)。

算法实现:

技巧(模型)

  1. 边权化点权:
    根据树的性质,每个节点有且仅有一个父节点,所以可以把当前节点与其父节点的边权视为当前节点的点权。特别地,在边转点之后,树链剖分的统计会出现变化(路径不能包含两端点)。

应用(习题)

0.\(O(logn)\)求LCA

思路:

  1. 求LCA(x,y),先判断两点是否在同一条链上(即对于top值是否相同)
  2. 是->深度较小的节点即为两点LCA
    否->链首深度较大的节点跳到所在链的链首节点的父节点(深度条件约束了链首为根节点时必不会跳跃),回到步骤1判断,直到求出LCA
实现
#include <cstdio>
#include <algorithm>
using namespace std;
const int NN = 500005;
int N , M , S , cnt = 0;
int ax , by;
struct E{
	int to ,nxt;
}e[ 2 * NN ];
int head[NN] , fa[NN] , dep[NN] , siz[NN] , hson[NN] ,top[NN];
void adde( int u , int v ) {
	e[ ++ cnt].nxt = head[u];
	e[ cnt ].to = v; 
	head[u] = cnt;
	return ;
}
void tree_inti( int x ) {
	hson[x] = -1;//标记叶节点 
	siz[x] = 1;
	for( int i = head[x] ; i ; i = e[i].nxt ) {
		if( !dep[ e[i].to ] && e[i].to != fa[x] ) {
			dep[ e[i].to ] = dep[x] + 1;
			fa[ e[i].to ] = x;
			tree_inti( e[i].to );
			siz[x] += siz[ e[i].to ];
			if( hson[x] == -1 || siz[ e[i].to ] > siz[ hson[x] ] )
				hson[x] = e[i].to;
		}
	}
	return ;
}
void tree_decomposition( int x , int TOP ) {
	top[x] = TOP;//应初始化为节点本身 
	if( hson[x] ) //重子节点非叶节点 
		tree_decomposition( hson[x] , TOP );//沿重子节点延伸重链 
	for( int i = head[x] ; i ; i = e[i].nxt ) //跨过轻边开新重链 
		if( e[i].to != fa[x] && e[i].to != hson[x] )
			tree_decomposition( e[i].to , e[i].to );
	return ;
}
int LCA( int x , int y ) {
	while( top[x] != top[y] ) {
		if( dep[ top[x] ] > dep[ top[y] ] )//链首深度较大的节点跳到其链首的父节点 
			x = fa[ top[x] ];
		else
			y = fa[ top[y] ];
	} 
	return dep[x] < dep[y] ? x : y;
}
int main() {
	scanf("%d%d%d",&N,&M,&S);
	for( int i = 1 ; i <= N - 1 ; i ++ ) {
		scanf("%d%d",&ax,&by);
		adde( ax , by ) , adde( by , ax );
	}
	tree_inti(S);
	tree_decomposition( S , S );
	fa[S] = S;
	for( int i = 1 ; i <= M ; i ++ ) {
		scanf("%d%d",&ax,&by);
		printf("%d\n",LCA( ax , by ) );
	}
	return 0;
}
  1. 求路径权值最大(边权转点权)
实现(洛谷A,SPOJ莫名因多组数据WA)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int NN = 100020;
int T , N , M , S , cnt , tot;
int ax , by;
struct E{
	int from , to , nxt;
}e[ 2 * NN ];
int c[NN] , head[NN] , fa[NN] , dep[NN] , siz[NN] , hson[NN] , top[NN] ;
int dfn[NN] , rk[NN];//dfn表示节点对应的dfs序号,rk表示dfs序对应的节点编号。 
char cc[10];
int Sgm_maxTree[ NN << 2 ];
void fread( int &n ) {
	n = 0;
	int f = 1;
	char c = getchar();
	while( c < '0' || c > '9' ) {
		f = ( c == '-' ? -1 : 1 );
		c = getchar();
	}
	while( c >= '0' && c <= '9' ) {
		n = ( n << 1 ) + ( n << 3 ) + c - '0';
		c = getchar();	
	}
	return ;
}
void push_up( int t ) {
	Sgm_maxTree[t] = max( Sgm_maxTree[ t << 1 ] , Sgm_maxTree[ t << 1 | 1 ] );
	return ;
}
void build( int l , int r , int t ) {
	if( l == r ) {
		Sgm_maxTree[t] = c[ rk[l] ]; 
		return ;
	} 
	build( l , ( l + r ) >> 1 , t << 1 );
	build( ( ( l + r ) >> 1 ) + 1 , r , t << 1 | 1 );
	push_up(t);
	return ;	
}
void change( int ti , int to , int l , int r , int t ) {
	if( l == r ) {
		Sgm_maxTree[t] = ti;
		return ;
	}
	int mid = ( l + r ) >> 1;
	if( to <= mid ) 
		change( ti , to , l , mid , t *2 );
	else {
		
		change( ti , to , mid + 1 , r , t * 2 + 1 );
	}
	push_up(t);
}
int getmax( int L , int R , int l , int r , int t ) {
	if( r < L || l > R )
		return -1234567;
	if( L <= l && r <= R )
		return Sgm_maxTree[t];
	int ans = 0 , mid = ( l + r ) >> 1;
	if( L <= mid )
		ans = max( ans , getmax( L , R , l , mid , t * 2 ) );
	if( r > mid )
		ans = max( ans , getmax( L , R , mid + 1 , r , t * 2 + 1 ) );
	return ans;
}
inline void adde( int u , int v ) {
	e[ ++ cnt].nxt = head[u];
	e[ cnt ].from = u;
	e[ cnt ].to = v; 
	head[u] = cnt;
	return ;
}
void tree_inti( int x ) {
	siz[x] = 1;
	for( int i = head[x] ; i ; i = e[i].nxt ) {
		if( !dep[ e[i].to ] && e[i].to != fa[x] ) {
			dep[ e[i].to ] = dep[x] + 1;
			fa[ e[i].to ] = x;
			tree_inti( e[i].to );
			siz[x] += siz[ e[i].to ];
			if( siz[ e[i].to ] > siz[ hson[x] ] )
				hson[x] = e[i].to;
		}
	}
	return ;
}
void tree_decomposition( int x , int TOP ) {
	if( dfn[x] != 0 )
		return ;
	tot ++;
	dfn[x] = tot;
	rk[tot] = x;
	top[x] = TOP;//应初始化为节点本身 
	if( hson[x] ) //优先对重子节点DFS保证同一重链dfs序连续(if保证重子节点非叶节点) 
		tree_decomposition( hson[x] , TOP );//沿重子节点延伸重链 
	for( int i = head[x] ; i ; i = e[i].nxt ) //跨过轻边开新重链 
		if( e[i].to != fa[x] && e[i].to != hson[x] )
			tree_decomposition( e[i].to , e[i].to );
	return ;
}
int tree_query( int x , int y ) {
	int ans = 0;
	while( top[x] != top[y] ) {
		if( dep[ top[x] ] < dep[ top[y] ] )// 每次选择深度较大的链往上跳,直到两点在同一条链上。 
			swap( x , y );
		ans = max( ans  , getmax( dfn[ top[x] ] , dfn[x] , 1 , N , 1 ) );
		x = fa[ top[x] ];
	}
	if( dep[x] > dep[y] )
		swap( x , y );
	ans = max( ans , getmax( dfn[x] + 1 , dfn[y] , 1 , N , 1 ) );
	return ans;
} 
inline void clean() {
	cnt = tot = 0;
	memset( e , 0 , ( 2 * N ) * sizeof(E) );
	memset( Sgm_maxTree , 0 , ( 4 * N  ) * sizeof(int) );
	for( int i = 1 ; i <= N ; i ++ )
		c[i] = top[i] = head[i] = dep[i] = dfn[i] = fa[i] = hson[i] = siz[i] = rk[i] = 0;
	return ;
}
int main() {
	fread(N);
	clean();
	for( int i = 1 ; i <= N - 1 ; i ++ ) {
		fread(ax) , fread(by);
		fread( c[by] );
		adde( ax , by ) , adde( by , ax );
	}
	tree_inti(1);
	tree_decomposition( 1 , 1 );
	fa[1] = 1;
	build( 1 , N , 1 ); 
	while(1) {
		scanf("%s",cc);
		if( cc[0] == 'D' )
			break;
		fread(ax) , fread(by);
		if( cc[0] == 'Q' )
			printf("%d\n",tree_query( ax , by ) );
		else {
			int ni;
			if( dep[ e[ ax * 2 ].from ] > dep[ e[ ax * 2 ].to ] )
				ni = e[ ax * 2 ].from;
			else
				ni = e[ ax * 2 ].to;
			change( by , dfn[ ni ] , 1 , N , 1 );
		}
	}
	return 0;
}
posted @ 2022-08-08 08:52  _画生  阅读(93)  评论(0编辑  收藏  举报