树链剖分笔记

树链剖分分为两种:重链剖分、长链剖分。

其中重链剖分更为常见。

先来了解几个概念。

(1) 重儿子:对于一个非叶子节点,它儿子中,子树最大的是重儿子。

(2) 轻儿子:去重儿子以外的都是轻儿子。

(3) 重边:连着两个重儿子的边。

(4) 重链:重边组成的链。

(5) 链头:一条重链上最上面的节点,也就是深度最小的。

先 dfs 找到重儿子,并预处理子树大小,再 dfs 记录 dfs 序,链头。第二次 dfs 优先遍历重儿子,这样可以做到一条重链上的 dfs 序是连续的。

void dfs1(int x,int father)
{
	dep[x]=dep[father]+1;//预处理深度 
	fa[x]=father;//预处理父亲 
	siz[x]=1;//计算子树大小 
	for(auto i:v[x])
	{
		if(i==father) continue;
		dfs1(i,x);
		siz[x]+=siz[i];
		if(siz[son[x]]<siz[i]) son[x]=i;//找重儿子,并记录它的编号 
	}
}
void dfs2(int x,int topx)
{
	id[x]=++cnt;//时间戳(dfs序) 
	a_new[cnt]=a[x];//新的a的值 
	top[x]=topx;//链头 
	if(!son[x]) return;//叶子节点,不用继续 
	dfs2(son[x],topx);//先遍历重儿子 
	for(auto i:v[x])
	{
		if(i==fa[x]) continue;
		if(i==son[x]) continue;
		dfs2(i,i);//没个轻儿子就是链头 
	}
}

然后来看模板题

这个东西肯定是用数据结构维护,我们先按 dfs 建线段树。

先看和子树有关的两个操作,我们知道一个子树中的 dfs 是连续的,那么操作的区间是 id_xid_x+siz_x-1

再看和链有关的两个操作, xy 的链,我们令 x 所在重链深度更大,那么让 x 跳到链头,然后用线段树把这一段修改或记录答案,因为我们先遍历重儿子所以重链的 dfs 序都是连续的,然后我们跳到 x 的父亲处来到另一条重链,重复执行以上操作直到 xy 处在同一条重链上,最后在维护一下 xy 这片区间就行。

会跳几次呢?

可以注意到链头一定是轻儿子或根,因为如果是重儿子肯定可以继续往上。

因为是轻儿子所以它不可能达到父亲子树大小的一半,如果达到一半了,那么它肯定是重儿子,所以每次跳跃子树大小至少乘 2 ,所以跳不超过 O(\log_{2}n) 次,一般来说次数是接近 O(1) 的。

要用线段树维护所以最终时间复杂度是 O(q\log_{2}^{2}n)

四个操作的代码:

int query_chain_add(int x,int y)//链求和
{
	int ans=0;
	while(top[x]!=top[y])//如果两点所在的重链的不一样,也就是链头不一样
	{
		if(dep[top[x]]<dep[top[y]]) swap(x,y);//强制x所在链的链头深度更大
		ans+=query_add(1,n,id[top[x]],id[x],1);
		//因为先遍历重儿子,所以重链dfs序也是连续的,跳到链头就将这个区间统计到和里 
		x=fa[top[x]];//跳到链头再跳到他的父亲,现在就在另一条重链上 
	}
	if(dep[x]>dep[y]) swap(x,y);
	ans+=query_add(1,n,id[x],id[y],1);//最后将x到y区间记录到答案中
	return ans;//返回答案 
}
int query_chain_max(int x,int y)//链最大值 
{
	int ans=-1e9;
	while(top[x]!=top[y])//如果两点所在的重链的不一样,也就是链头不一样
	{
		if(dep[top[x]]<dep[top[y]]) swap(x,y);//强制x所在链的链头深度更大
		ans=max(ans,query_max(1,n,id[top[x]],id[x],1));
		//因为先遍历重儿子,所以重链dfs序也是连续的,跳到链头就将这个区间统计到最大值里 
		x=fa[top[x]];//跳到链头再跳到他的父亲,现在就在另一条重链上 
	}
	if(dep[x]>dep[y]) swap(x,y);
	ans=max(ans,query_max(1,n,id[x],id[y],1));//最后将x到y区间记录到答案中
	return ans;//返回答案 
}
void update_tree(int x,int k)//修改子树 
{
	update(1,n,id[x],id[x]+siz[x]-1,1,k);//子树的dfs序是连续的 
}
int query_tree(int x)//子树求和
{
	return query_add(1,n,id[x],id[x]+siz[x]-1,1);//返回子树和,子树的dfs序是连续的 
} 
4 个赞

更好的阅读体验

1 个赞

明天讲AC自动机(明天上课上AC自动机)

%%%(明天能讲自动AC机吗?不是AC自动机

这个东西AI会,你问AI啥,它能让你AC

好像说之前有人用C++编出了自动AC机? @金杭东 C++真的是先得了吗?

AI不就是程序编的嘛。
C++还能编出C++呢

o对呀,说的好有道理,但是所有题目都能AC是不是有点奇怪?难道他利用了#include<window.h> 那内置文件改变了?

直接爬虫爬题解就行了

@张乐凡 但是抄了题解,会被发现呀,他没有

你问AI问题,它不是基本都能打出来吗,所以造个AI就大概是个自动AC机了。

我先在学回文自动机,今天可能来不及发AC自动机笔记了

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%。

是重边吧

1 个赞

笔误