还在更新。
动态开点线段树
众所周知,一棵线段树会包含很多节点,但是有很多节点都用不上,这样就会造成空间的浪费,有什么办法解决空间浪费呢?就是当我们需要的时候再去开点,不预先建树。
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);
}
}
}
这样以来就成功的划分了链并且使得一条链上的编号是连续的了。
严格来说后面的部分不属于树剖,所以先鸽了。