树链剖分就是把树拆成一系列链,然后用数据结构对链进行维护。大概是树上套数据结构,不同题有不同做法,可以套树状数组、线段树、堆…… 一般题都可以套线段树解决
时间不多,研究的不是很透彻,大概讲一下。
树链剖分主要解决 在一棵树上进行路径修改、求极值、求和等问题,拓展还可以解决很多其他问题。 很重要一件事,,,,,树剖不能删边或增边,删边增边的学LCT
开始正题
定义:
Siz[x] :以x为根的子树的节点数
Son[x] :x的子节点中siz值最大的(x的重儿子)
Fa[x] :x的父亲节点
Dep[x] :x的深度 ( root深度根据情况定 )
轻儿子 :x除重儿子外其他子节点
重边 :x与重儿子的连边
轻边 :x与轻儿子的连边
重链 :重边连成的路径
轻链 :轻边连成的路径
Top[x] :x所在重链的顶端节点(如果x是轻儿子,Top[x]为它自己)
W[x] :x与父亲的连边
剖分后的树有如下性质:
性质1:如果(v,u)为轻边,则siz[u] * 2 < siz[v];
性质2:从根到某一点的路径上轻链、重链的个数都不大于logn。
算法实现(摘取了网上的部分文字、图):
我们可以用两个dfs来求出fa、dep、siz、son、top、w。
dfs:把fa、dep、siz、son求出来,比较简单,详见标程。
build_tree:
⒈对于x,当son[x]存在(即v不是叶子节点)时,显然有top[son[x]] = top[x]。线段树中,x的重边应当在x的父边的后面,记w[son[x]] = ++z,z表示最后加入的一条边在线段树中的位置。此时,为了使一条重链各边在线段树中连续分布,应当进行build_tree(son[x]);
⒉对于x的各个轻儿子y,显然有top[y]] = y,并且w[y] = ++z,进行build_tree过程。
这就求出了top和w。
将树中各边的权值在线段树中更新,建链和建线段树的过程就完成了。
修改操作:例如将x到y的路径上每条边的权值都加上某值c。
记f1 = top[x],f2 = top[y]。
当f1 <> f2时:不妨设dep[f1] >= dep[f2],那么就更新u到f1的父边的权值(logn),并使x = fa[f1]。
当f1 = f2时:x与y在同一条重链上,若x与y不是同一点,就更新x到y路径上的边的权值(logn),否则修改完成;
重复上述过程,直到修改完成。
如图所示,较粗的为重边,较细的为轻边。节点编号旁边有个红色点的表明该节点是其所在链的顶端节点。边旁的蓝色数字表示该边在线段树中的位置。图中1-4-9-13-14为一条重链。
当要修改11到10的路径时( 具体修改、询问边(或点)的值用数据结构,如例题就是用线段树更新、询问边 ):
第一次迭代:x = 11,y = 10,f1 = 2,f2 = 10。此时dep[f1] < dep[f2],因此修改线段树中的5号点,y= 4, f2 = 1;
第二次迭代:dep[f1] > dep[f2],修改线段树中10--11号点。x = 2,f1 = 2;
第三次迭代:dep[f1] > dep[f2],修改线段树中9号点。x = 1,f1 = 1;
第四次迭代:f1 = f2且x = y,修改结束。
例一:Qtree1
树链剖分模板题:
注意这题线段树维护的是边的值
因为线段树的root不一定是树的root,所以update,maxi这些关于线段树的操作不要代root进去
Code:
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn = 101000 ;
struct edge
{
int y,next;
}a[maxn<<1]; int len,first[maxn];
int siz[maxn],son[maxn],fa[maxn],dep[maxn],top[maxn],w[maxn];
int d[maxn][3];
struct node
{
int l,r,c;
}tree[maxn<<2]; //线段树
int root,n,z;
void insert( int x,int y ) //建边
{
len++;
a[len].y = y;
a[len].next = first[x]; first[x] = len;
}
void dfs( int x )
{
siz[x] = 1; // siz值包括自己
son[x] = 0;
for( int k=first[x];k;k=a[k].next )
{
int y=a[k].y;
if( y != fa[x] ) //y不为父亲节点
{
fa[y] = x;
dep[y] = dep[x]+1; //更新fa,dep
dfs( y );
if( siz[son[x]] < siz[y] ) son[x] = y;
//如果son[x]的siz小于y的siz,更新son[x]
siz[x] += siz[y]; // 更新siz[x]
}
}
}
void build_tree( int x,int tp )
{
w[x] = ++z;
top[x] = tp;
if( son[x] ) build_tree( son[x],tp ); // 在一条重链上的边编号要重复,这样进行修改、询问操作时才能用线段树
for( int k=first[x];k;k=a[k].next )
{
int y = a[k].y;
if( y != son[x] && y != fa[x] )
build_tree( y,y ); //轻儿子的top为他自己
}
}
void build_tree2( int x,int l,int r ) //建线段树
{
tree[x].l = l; tree[x].r = r;
if( l == r ) return;
int mid = ( l+r )>>1, lc = x<<1, rc = lc+1;
build_tree2( lc,l,mid );
build_tree2( rc,mid+1,r );
}
void update( int x,int loc,int c ) //更改边的值
{
if( tree[x].l == tree[x].r )
{
tree[x].c = c;
return ;
}
int mid = ( tree[x].l + tree[x].r ) >>1, lc = x<<1, rc = lc+1;
if( loc <= mid ) update( lc,loc,c );
else update( rc,loc,c );
tree[x].c = max( tree[lc].c , tree[rc].c ); //更新
}
int maxi( int x,int lx,int rx ) //寻找 第lx条边 到 第rx条边 的最大值
{
if( rx < tree[x].l || tree[x].r < lx ) return 0; //判断是否越界
if( lx <= tree[x].l && tree[x].r <= rx ) return tree[x].c; //在范围内直接return
int mid = ( tree[x].l + tree[x].r ) >>1, lc = x<<1, rc = lc+1;
return max( maxi( lc,lx,rx ) , maxi( rc,lx,rx ) );
}
int Query( int x,int y )
{
int tmp = 0, f1 = top[x], f2 = top[y];
while( f1 != f2 ) //类似LCA的操作,两个点向上跳到同一条重链上,过程中记录最大值
{
if( dep[f1] < dep[f2] )
{
swap( f1,f2 );
swap( x,y );
}
tmp = max( tmp, maxi( 1,w[f1],w[x] ) ); //因为建树时同一条重链的边的编号是挨着的,所以可以直接线段树询问
x = fa[f1];
f1 = top[x];
}
if( x == y ) return tmp;
if( dep[x] > dep[y] ) swap( x,y ); //调整x、y的位置,使x在y的上方,方便下面的操作
return max( tmp, maxi( 1,w[son[x]],w[y] ) ); //在同一条重链上,两点之间的距离的最大值
}
int main()
{
int i,j,x,y,c,m;
char st[50];
scanf("%d",&m);
while( m-- )
{
root = 1;
memset( tree,0,sizeof tree );
memset( first,0,sizeof first );
dep[root] = fa[root] = len = z = 0; //初始化 , dep的值依题而定
scanf("%d",&n);
for( i=1;i<n;i++ )
{
scanf("%d%d%d",&x,&y,&c);
d[i][0] = x;
d[i][1] = y;
d[i][2] = c;
insert( x,y );
insert( y,x );
}
dfs( root );
build_tree( root,root );
build_tree2( 1,1,z );
for( i=1;i<n;i++ )
{
if( dep[d[i][0]] > dep[d[i][1]] ) //因为是无向边,所以需要判断哪个是父亲
swap( d[i][0],d[i][1] );
update( 1,w[d[i][1]],d[i][2] ); //更新边的权值
}
while( 1 )
{
scanf("%s",st);
if( st[0] == 'D' ) break;
scanf("%d%d",&x,&y);
if( st[0] == 'C' )
update( 1,w[d[x][1]],y ); //change改变的是输入的第i条边的值
else printf("%d\n",Query( x,y ));
}
}
return 0;
}
(代码顺序尽量不要改)
树剖有些题要维护的是点的值,这时候可以把点的值赋给它的w[],不过更新和询问有部分要修改。
如果是无根树可以随便定root,如果有根要找根。
部分文字、图片摘自网络,见谅