树链剖分学习笔记
树链剖分(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\)的重链 - 每一条重链以轻儿子/根为起点;
- 如果\((u,v)\)是一条轻边,那么\(size(v)<size(u)/2\);
- 从根结点到任意结点的路所经过的轻重链的个数必定都小于等于\(logn\)(完全二叉树可以卡满)(证明:每遇到一条轻边,子树大小就会减半,详见oiwiki)。
- 落单的节点一般算作单独一条重链;
算法实现:
技巧(模型)
- 边权化点权:
根据树的性质,每个节点有且仅有一个父节点,所以可以把当前节点与其父节点的边权视为当前节点的点权。特别地,在边转点之后,树链剖分的统计会出现变化(路径不能包含两端点)。
应用(习题)
0.\(O(logn)\)求LCA
思路:
- 求LCA(x,y),先判断两点是否在同一条链上(即对于top值是否相同)
- 是->深度较小的节点即为两点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;
}
- 求路径权值最大(边权转点权)
实现(洛谷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;
}