树链剖分

树链剖分就是把树拆成一系列链,然后用数据结构对链进行维护。大概是树上套数据结构,不同题有不同做法,可以套树状数组、线段树、堆…… 一般题都可以套线段树解决

 

时间不多,研究的不是很透彻,大概讲一下。

树链剖分主要解决  在一棵树上进行路径修改、求极值、求和等问题,拓展还可以解决很多其他问题。     很重要一件事,,,,,树剖不能删边或增边,删边增边的学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来求出fadepsizsontopw

    dfs:把fadepsizson求出来,比较简单,详见标程。

build_tree

⒈对于x,当son[x]存在(即v不是叶子节点)时,显然有top[son[x]] = top[x]。线段树中,x的重边应当在x的父边的后面,记w[son[x]] = ++zz表示最后加入的一条边在线段树中的位置。此时,为了使一条重链各边在线段树中连续分布,应当进行build_tree(son[x])

    ⒉对于x的各个轻儿子y,显然有top[y]] = y,并且w[y] = ++z,进行build_tree过程。

           这就求出了topw

    将树中各边的权值在线段树中更新,建链和建线段树的过程就完成了。

 

    修改操作:例如将xy的路径上每条边的权值都加上某值c

    记f1 = top[x]f2 = top[y]

    当f1 <> f2时:不妨设dep[f1] >= dep[f2],那么就更新uf1的父边的权值(logn),并使x = fa[f1]

    当f1 = f2时:xy在同一条重链上,若xy不是同一点,就更新xy路径上的边的权值(logn),否则修改完成;

    重复上述过程,直到修改完成。

  

 

    如图所示,较粗的为重边,较细的为轻边。节点编号旁边有个红色点的表明该节点是其所在链的顶端节点。边旁的蓝色数字表示该边在线段树中的位置。图中1-4-9-13-14为一条重链。

 

    当要修改1110的路径时 具体修改、询问边(或点)的值用数据结构,如例题就是用线段树更新、询问边 ):

    第一次迭代:x = 11y = 10f1 = 2f2 = 10。此时dep[f1] < dep[f2],因此修改线段树中的5号点,y= 4, f2 = 1

    第二次迭代:dep[f1] > dep[f2],修改线段树中10--11号点。x = 2f1 = 2

    第三次迭代:dep[f1] > dep[f2],修改线段树中9号点。x = 1f1 = 1

第四次迭代:f1 = f2x = y,修改结束。

 

例一:Qtree1

 

树链剖分模板题:

注意这题线段树维护的是边的值

因为线段树的root不一定是树的root,所以updatemaxi这些关于线段树的操作不要代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,如果有根要找根。


部分文字、图片摘自网络,见谅

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
提供的源码资源涵盖了小程序应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值