树链剖分

树剖是个神奇的东西~

其实也没有那么神奇~

首先要知道树剖是什么:将一颗树分成若干条链后,对每一个链用数据结构进行维护。

我们最常用的就是开一颗线段树保存所有树链(显然我们要保证有序)

如何分链?dalao们称它叫启发式合并,什么意思呢?

对于一颗以v为根的子树,我们选择它若干儿子中,儿子的儿子数(包括儿子自己)最多的那一个儿子与v相连直到叶子节点,这么一条路径我们称它为重路径,路径上的边我们成为重边,其余的则称轻边。

#define FOR(i,L,R) for(register int i=(L);i<=(R);++i)
int n,m,cnt;
struct Graph{//构图finish 
	vectorG[N];
	int p[N],fa[N];
	int siz[N],son[N],dep[N];
	int top[N];
	#define to G[x][i]
	inline void dfs2(int x,int sp){
		top[x]=sp;//我们记录每一条链的链顶,对于每个轻儿子,他的top等于自己 
		p[x]=++cnt;//线段树上的映射 
		if(son[x])dfs2(son[x],sp);
		int sz=G[x].size()-1;
		FOR(i,0,sz)if(to^fa[x]&&to^son[x])dfs2(to,to);
	}
	inline void dfs1(int x,int Fa,int depth){
		int sz=G[x].size()-1;
		FOR(i,0,sz)if(to^Fa){
			dfs1(to,fa[to]=x,dep[to]=depth+1);
			siz[x]+=siz[to];//更新size值 
			if(siz[son[x]]

我们简称v的轻儿子为ls,重儿子为ws

由轻重儿子的性质易知,size(ls)

对于我们树剖的操作过程,在此我们我们以树上求和为例(修改和查询一模一样,只是换函数名而已):

#define top(a) g.top[a]
#define dep(a) g.dep[a]
#define p(a) g.p[a]
inline void GETSUM(int x,int y){
	int t1=top(x),t2=top(y);
	int ans=0;
	while(t1!=t2){
		if(dep(t1)dep(y))swap(x,y);//当在一条链上时,我们就只用操作现在的x~y区间 
	cout<

显然我们的操作最多向上爬2log2(n)次,因为轻重儿子的性质决定了向上爬的次数,我们每一次修改是log2(n)的,因此树剖的时间复杂度大概是log2(n)^2,还算优秀

只要会了线段树,树剖就ok,因此线段树才是基础。

你可能感兴趣的:(数据结构,树剖,树链剖分)