树链剖分

前言

树链剖分:在一颗树上两点之间的路径的修改、求值。

原理

将一课树分成若干条链,将它们连起来,形成一条链,再用线段树等方法来维护、求值。


定义

在熟练剖分中,会使用到很多数组,这是它们的作用:

size[x]:在以x为根的子树中的节点的个数
son[x]:x的重儿子
deep[x]:x的深度
fa[x]:x的父亲
top[x]:x所在的链的顶部节点
aft[x]:x在合成的新链中的编号
bef[x]:x在树上的编号

那么重儿子就是某个节点的儿子中size[]值最大的节点,重边就是它们的之间的边,由重边连起来的链就是重链。那么其余的节点就是轻儿子轻边就是它们的之间的边。

性质

一、轻边(u,v),size(v)<=size(u)/2。
二、从根到某一点的路径上,不超过O(logN)条轻边,不超过O(logN)条重路径。

实现

预处理

首先用一个dfs把size[]、son[]、deep[]、fa[]求出来。

int dg(int x)
{
	size[x]=1;
	int mx=0;
	for(int i=last[x];i;i=next[i])
	{
		if(to[i]!=fa[x])
		{
			fa[to[i]]=x;
			deep[to[i]]=deep[x]+1;
			dg(to[i]);
			size[x]+=size[to[i]];
			if(size[to[i]]>mx)
			{
				mx=size[to[i]];
				son[x]=to[i];
			}
		}
	}
}

再用一个dfs求top[]、aft[]、bef[]以及重链
从每一个轻儿子开始,沿着重边往下拉,直到拉到叶子节点为止。
最后,将所有的重链首尾相接,形成一条新的链,按照顺序,给每一个点一个新的编号。

int dg1(int x)
{
	aft[x]=++tot;
	d[tot]=x;
	if(top[x]==0)
	{
		top[x]=x;
	}
	if(son[x])
	{
		top[son[x]]=top[x];
		dg1(son[x]);
	}
	for(int i=last[x];i;i=next[i])
	{
		if(to[i]!=fa[x] && to[i]!=son[x])
		{
			dg1(to[i]);
		}
	}
}

修改、查询操作

当修改或查询u、v两个点的路径时
(1)当u、v在同一条重链上,即top[u]=top[v],显然直接修改aft[u]和aft[v]之间的值。
(2)当u、v不在同一条重链上,即top[u]<>top[v],如果deep[top[u]]>deep[top[v]],就修改aft[top[u]]和aft[u]之间的值,并将u赋值为fa[top[u]]。反之。
反复做(2)操作,直到变成(1)的情况。
时间复杂度O(NlogN)。


【ZJOI2008】树的统计
这道题可以用来练手,挺好滴。

原文地址:https://www.cnblogs.com/chen1352/p/9026692.html