树链剖分(重链剖分法)

简单的讲一下重剖。

模板题:ZJOI 2008 树的统计 + USACO 2011 Dec Gold 种草

Chapter Former 声明

在此之前,我们先声明以下定义:

重儿子:一个节点的子树大小最大的子节点

重边:链接重儿子的边(自然而然,轻边就是树中不是重边的边)

重链:一堆重儿子和重边组成的链。为了避免一个点的特殊情况,我们规定一个单独的点也成一个重链。

接下来是树剖的代码思路:

Chapter I 处理重链

  • 第一步:找重儿子和重边

首先我们需要找到重儿子。我们用一个深搜来遍历整棵树,找到每个节点的重儿子。

判断搜到的新的子节点是否是这个节点的当前真正重儿子的方法:计算子树大小,然后比较。

在遍历每个子节点时,这个节点的子树大小要加上节点的子树大小。一开始每个节点的子树大小都为1

代码写起来是这样的:

void dfs1(int x,int fath){
	siz[x]=1;//初始大小 
	for(int i=head[x];i;i=edge[i].next){//链式前向星存图 
		int exi=edge[i].to;
		if(exi!=fath){//查回父亲也有可能 
			dfs1(exi,x);
			siz[x]+=siz[exi];
			if(siz[son[x]]<siz[exi]){//如果当前已知重儿子的子树大小比该子节点子树大小小 
				son[x]=exi;//替换重儿子 
			}
		}
	} 
}

为后续方便,我们需要处理dep数组(存储每个节点的深度)和fa数组(每个节点的父亲),所以可以一并写进这个dfs里:

void dfs1(int x,int fath,int depth){
	siz[x]=1;//初始大小 
	fa[x]=fath;//写好父亲 
	dep[x]=depth;//写好深度 
	for(int i=head[x];i;i=edge[i].next){//链式前向星存图 
		int exi=edge[i].to;
		if(exi!=fath){//查回父亲也有可能 
			dfs1(exi,x,depth+1);//更深一层 
			siz[x]+=siz[exi];
			if(siz[son[x]]<siz[exi]){//如果当前已知重儿子的子树大小比该子节点子树大小小 
				son[x]=exi;//替换重儿子 
			}
		}
	} 
}
  • 第二步:链接重边,形成重链并计算各种值

为了方便后续操作,我们还需要建立一个关于节点的新的dfn序,以及存储每个节点的重链顶端节点(为了后面跳lca)。

所以我们需要再写个dfs,处理重链,新的dfn序,以及存储每个节点的重链定端节点。

void dfs2(int x,int nowtop){
	dfn[x]=++labor;//labor用于辅助给dfn赋值 
	top[x]=nowtop;//当前节点的重链顶端
	if(!son[x])
		return;//叶子结点,因为没有重儿子,所以没有儿子。
	dfs2(son[x],nowtop);//为了保证dfn序可以在重链上连续,我们优先搜索重儿子
	for(int i=head[x];i;i=edge[i].next){
		int exi=edge[i].to;
		if(exi!=fa[x]&&exi!=son[x]){
			dfs2(exi,exi);//从exi开始,开辟一条新的重链,链顶为exi自身 
		}
	} 
}

Chapter II 维护重链

那么接下来我们需要维护重链。那怎么维护啊?

剖分完之后,我们发现,每条重链就相当于一段编号连续的区间,用数据结构
(比如线段树和平衡树这种恶俗东西)去维护。

把所有的重链首尾相接,放到同一个数据结构上,然后维护这一个整体即可。


Chapter III 修改查询

最后是修改和查询。

  • 单独修改一个点的权值

根据新的编号直接在数据结构中修改就行了。

  • 修改从 u u u v v v 的路径上(含 u , v u,v u,v)每个点的权值

我们分类讨论。

  • 如果 u , v u,v u,v 在同一条重链上

    此时可以直接用数据结构修改 d f n u , d f n v dfn_u,dfn_v dfnu,dfnv 一段的值。

    if(dep[v]>dep[u])swap(v,u);
    Modify_SegTree(dfn[v],dfn[u]);
    
  • 如果 u , v u,v u,v 不在同一条重链上

    我们就想方设法把 u , v u,v u,v 往一条重链上套。在套的过程中,一边修改沿途路径。
    最后按照 u , v u,v u,v 在同一条重链上的处理方法处理一次即可完成修改。

    那么怎么把 u , v u,v u,v 往同一条重链上套?

    我们最喜欢套娃分类讨论。

    • u u u 所在的重链的父亲(记作 w w w) 与v在同一条重链上。

      此时可以修改 u , w u,w u,w 之间的权值,再对于 w , v w,v w,v 执行“在同一条重链上的操作”即可。

      也就是说,让 u u u 直接跳到 v v v 所在的重链上,然后变成“ u , v u,v u,v 在同一条重链上”的情况。

      Modify_SegTree(dfn[top[u]],dfn[u]);
      u=fa[top[u]];
      if(dep[v]>dep[u])swap(v,u);
      Modify_SegTree(dfn[v],dfn[u]);
      
    • w w w v v v 不在一条重链上

      那就一直重复以下操作:

      • 找到重链顶端 (记作 t o p u top_u topu

      • 修改 u , t o p u u,top_u u,topu 路径上的点的权值

      • 跳转 u u u w w w w = t o p u w = top_u w=topu 的父亲节点)

      直到 u u u v v v 在同一条重链上,然后处理即可。

      while(top[v]!=top[u])
      {
      Change_SegTree(dfn[top[u]],dfn[u]);
      u=fa[top[u]];
      }
      if(dep[v]>dep[u])swap(v,u);
      Change_SegTree(dfn[v],dfn[u]);
      
    • u u u v v v 都需要往上跳重链才能满足 u u u v v v 在同一条重链上

      此时按照LCA的思路,每次选择深度大的点,进行“找到重链顶端 修改权值 跳转 u u u ”的操作,直到 u u u v v v 在同一条重链上,然后处理即可。

      while(top[v]!=top[u])
      {
      if (dep[top[v]]<dep[top[u]])swap(v,u); 
      Modify_SegTree(dfn[top[v]],dfn[v]); 
      v=fa[top[v]]; 
      }
      if(dep[v]>dep[u])swap(v,u);
      Change_SegTree(dfn[v],dfn[u]);
      
      

经过上述漫长的讨论,我们发现,之前的所有情况不过都是最后一种情况的特殊情况而已,所以在实际写题中,我们只需要使用最后一种情况的代码即可。

如果要和之前的dfs码与之后的询问并在一起,可以使用以下代码:

void modifyX (int x, int y ,int d) //将x到y路径上的所有点的权值修改为d
{
  while(top[x]!=top[y])
  {
    if (dep[top[x]]<dep[top[y]])swap(x,y); //选深度大的点往上跳
    modify(dfn[top[x]],dfn[x],d);
    //修改存储重链的数据结构,将指定区间的值改为d
    x=fa[top[x]]; //X往上跳
  }
  if(dep[x]>dep[y])swap (x,y);
  modify(dfn[x],dfn[y],d);
}

其中 modify 也就是 Modify_Segtree ,修改线段树

  • 询问

对于询问一个节点和询问一段路径,思路参照修改。

int queryX(int x,int y) //比如查询x到y路径上点权总和
{
  int Sum=0;
  while(top[x]!=top[y])
  {
  if(dep[top[x]]<dep[top[y]])swap(x,y);
  Sum+=query(dfn[top[x]],dfn[x]);
  x=fa[top[x]];
  }
  if(dep[x]>dep[y])swap(x,y);
  Sum+=query(dfn[x],dfn[y]);
return Sum;
}

其中 query 同理,是 修改线段树(Query_Segtree)


Chapter IV 创意工坊

下列是树剖的各个拓展用法:

  • 求LCA

在树剖过程中,我们会求到两个点的LCA,此时可以借机写一份求LCA的代码。

int getLCA(int x,int y)
{
  while(top[x]!=top[y])
  {
  if(dep[top[x]]<dep[top[y]])swap(x,y);
  x=fa[top[x]];
  }
  if(dep[x]>dep[y])swap(x,y);
  return x;
}

Chapter V 总结

树链剖分:把树的边映射到线段树上的数据结构。

求重链:用两个dfs处理树的信息,重边以及轻边,然后链接重链。

修改和查询:重复往上,一条一条重链地跳,找到LCA,途中用数据结构边跳边修/查。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值