关于萌新对树链剖分的理解 前言 树剖是什么

可能更优的阅读体验

仍在更新

关于我突然补起树剖这件事呢,其实是因为最近做的题可以用树剖做,结果发现我不会树剖,就开始恶补了树剖。

树剖是什么

树剖,即树链剖分。

树链剖分的定义:

一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每个点属于且只属于一条链,然后再通过数据结构(树状数组、BST、SPLAY、线段树等)来维护每一条链。——百度百科

首先声明几个定义:

  1. 重儿子:一个点的儿子中子树大小最大的儿子。
  2. 轻儿子:一个点的儿子中除了重儿子的儿子。(注:叶子节点既没有重儿子也没有轻儿子,因为他没有儿子)
  3. 重边:连接父亲和重儿子的边。
  4. 轻边:连接父亲和轻儿子的边。
  5. 重链:相邻重边连起来的,连接重儿子的一条链。(但是起点是轻儿子)

那么树链剖分可以处理什么问题呢?

  • 更改路径上的值
  • 查询一条路径的总和
  • 更改一个子树的值
  • 查询一个子树的值

先用两个 ( ext{dfs})​ 来预处理一些东西。

dfs1

这个 ( ext{dfs})​ 求出每个的点的父亲、深度、子树大小和重儿子。

void dfs1(int x,int fa)
{
	f[x]=fa;//父亲
    deep[x]=deep[fa]+1;//深度
    size[x]=1;//大小
	for (int i=a[x].head;i;i=a[i].next)
	{
		int v=a[i].to;
		if (v==fa) continue;
		dfs1(v,x);//深搜儿子
		size[x]+=size[v];//大小传递
		if (size[v]>=size[son[x]]) son[x]=v;//求出重儿子
	}
}

dfs2

这个 ( ext{dfs}) 记录每个点的新编号、新编号的值和所处重链的开头。

void dfs2(int x,int tp)
{ 
	top[x]=tp;//重链开头
	id[x]=++cnt;//新编号
    v1[cnt]=val[x];//重新赋值
	if (!son[x]) return;//叶子结点没有重儿子,结束
	dfs2(son[x],tp);//先重儿子后轻儿子,理由见下
	for (int i=a[x].head;i;i=a[i].next)
	{
		int v=a[i].to;
		if (v!=f[x]&&v!=son[x]) dfs2(v,v);//轻儿子的重链开头是他本身
	}
}

处理问题

那么在 ( ext{dfs2}) 中为什么要先走重儿子后走轻儿子呢?

模拟一下:

关于萌新对树链剖分的理解
前言
树剖是什么

容易看出图中的重儿子有 2、4、6、5、8,有四条重链(用红框框住的),同时由于先走重儿子的缘故,重链上的编号是连续的。