课堂笔记 2021.7.27-2021.7.28

还在更新。

动态开点线段树

众所周知,一棵线段树会包含很多节点,但是有很多节点都用不上,这样就会造成空间的浪费,有什么办法解决空间浪费呢?就是当我们需要的时候再去开点,不预先建树。

void build(int &i,int l,int r,int num,int k){
    
    
	if(!i)
	i=++ecnt;
	if(l==r){
    
    
		t[i].v+=k;
		return ;	
	} 
	int mid=(l+r)/2;
	if(num<=mid)
	build(t[i].ls,l,mid,num,k);//由于 i 是传的 &i,所以这里如果没有左右儿子就会新建且传回来
	else
	build(t[i].rs,mid+1,r,num,k);
	t[i].v=t[t[i].ls].v+t[t[i].rs].v; 
}

线段树合并

线段树的“加法”,当我们有两棵线段树,其中有两个节点维护了相同区间的相同信息时,它们是可以合并的,这样以来,就可以把两棵线段树合并了,譬如说两个连通块,分别用线段树维护了连通块中每个点权出现次数,当它们联通之后,我们就需要合并这两棵线段树,得到新的连通块的信息,具体实现是把第二棵线段树合到第一棵,并且记录每个点所对应线段树的根,在合并过程中修改即可:


int merge(int x,int y,int l,int r){
    
    
	if(!x)
	return y;
	if(!y)
	return x;
	if(l==r){
    
    
		t[x].v+=t[y].v;//为什么只合并叶子节点?因为答案会push_up
		return x;
	}
	int mid=(l+r)/2;
	t[x].ls=merge(t[x].ls,t[y].ls,l,mid);
	t[x].rs=merge(t[x].rs,t[y].rs,mid+1,r);
	t[x].v=t[t[x].ls].v+t[t[x].rs].v; 
	return x;
} 

树链剖分

回想一下,对于树上的修改问题,我们会通过什么方式求解?

dfs 序,欧拉序…

本质上我们是把树型结构转化为线性结构

想象一下,如果我们把一棵树剖成一条一条的链,对于解决树上路径修改问题,只需要一条链一条链的区间修改,不就很好吗?

于是树链剖分(下文简称树剖)因应而生,比较常用的是重链剖分,又叫轻重链剖分。

先来明确什么是一棵树的重量,一般来说,以 i i i 为根的子树大小就是 i i i 的重量。

重链剖分是这样的:对于每个节点,找到其重量最大的儿子节点,称之为重儿子,从父节点到重儿子的边称为重边,在草稿纸上画一画,很容易发现,多条重边经常是以一条链的形式出现,因此得到了重链的概念:我们称一条链为重链,当且仅当链上所有边都是重边。继续观察你的草稿纸,你会发现,重链之间夹杂了些许“零碎”的边,这就是轻边

结论1:在一棵有 n n n 个节点的树中所有轻边所指向的点(称为轻儿子)的重量不超过 ⌈ n 2 ⌉ \lceil \frac{n}{2} \rceil 2n

证明:反证法,设存在这样的一个节点,其有一个轻儿子重量大于 ⌈ n 2 ⌉ \lceil \frac{n}{2} \rceil 2n,根据对“重儿子”的定义,这个重儿子的重量也会大于 ⌈ n 2 ⌉ \lceil \frac{n}{2} \rceil 2n,二者相加,这个节点的重量就会大于 n n n,显然在这样一棵树中不会有一个重量大于 n n n 的节点。

结论2:一棵有 n n n 个节点的树上的一条路径,至多经过 log ⁡ 2 n \log_2 n log2n 条重链

证明:我们从一条重链走到另一条重链的过程中,根据定义,显然会经过一条轻边,根据结论 1,显然最多经过 log ⁡ 2 n \log_2 n log2n 条轻边,而重链的数量和轻边数量理论上时同阶的。

这样以来我们就把一棵树划成若干条链了,对于每条链分别进行维护,但实际实现上是通过两次 dfs:

  • 第一次 dfs:
void dfs1(int x,int fa){
    
    
	dep[x]=dep[fa]+1; 
	cnt[x]=1;
	Fa[x]=fa;
	for(int i=h[x];i;i=e[i].nxt){
    
    
		int v=e[i].v;
		if(v==fa)
		continue;
		dfs1(v,x);
		cnt[x]+=cnt[v];
		if(cnt[v]>mc[x]){
    
    
			mc[x]=cnt[v];
			Son[x]=v;
		}
	}
	return ;
}
  • 第二次 dfs:
void dfs2(int x,int fa,int as){
    
    
   top[x]=as;
   nid[x]=++tot2;
   if(Son[x])
   dfs2(Son[x],x,as);
   for(int i=h[x];i;i=e[i].nxt){
    
    
   	int v=e[i].v;
   	if(Son[x]!=v&&v!=fa){
    
    
   		dfs2(v,x,v);
   	}
   }
}  

这样以来就成功的划分了链并且使得一条链上的编号是连续的了。

严格来说后面的部分不属于树剖,所以先鸽了。

おすすめ

転載: blog.csdn.net/cryozwq/article/details/119192411