恶心至极
树链剖分
树链就是树上的路径。
将一棵树划分成若干条链,用数据结构(线段树,平衡树等)去维护每条链,复杂度为
。
重链剖分
剖分有三种方法:盲目剖分、随机剖分、启发式剖分。
综合比较,启发式剖分是剖分时的最佳选择。
定义
为以
为根的子树的节点个数。
令
为
的儿子节点中
值最大的节点,那么边
被称为重边,树中重边之外的边被称为轻边。
我们称某条路径为重路径(也叫重链),当且仅当它全部由重边组成。
对轻边
,
。
从根到某一点的路径上,不超过
条轻边,不超过
条重路径。
重链剖分的过程为2次dfs:
- 第一步,找重边(图中的红边)。通过一次dfs,可记下所有的重边。
- 第二步,连重边成重链。以根节点为起点,沿重边向下拓展,拉成重链。不在当前重链上的节点,都以该节点为起点向下重新拉一条重链。
记树中编号为 的结点在用来维护的数据结构中的新编号为 ,且 。 记录每个结点所在重链的起点(深度最小的结点), 记录每个结点的深度。之后会用到。
int id[maxn],rnk[maxn];
struct edge{
int to,pre;
edge(int to=0,int pre=0):to(to),pre(pre){};
};
struct tree{
edge e[maxn];
int now[maxn],edgtot=0,idtot=0;
int fa[maxn],son[maxn],dep[maxn],size[maxn],top[maxn];
void insert(int u,int v){
e[++edgtot]=edge(v,now[u]),now[u]=edgtot;
}
void find(int x,int cfa,int cdep){
fa[x]=cfa,dep[x]=cdep,size[x]=1,son[x]=0;
for(int i=now[x];i;i=e[i].pre){
int cson=e[i].to;
if(cson!=fa[x]){
find(cson,x,cdep+1);size[x]+=size[cson];
if(size[cson]>size[son[x]])son[x]=cson;
}
}
}
void connect(int x,int anc){
id[x]=++idtot,rnk[idtot]=x,top[x]=anc;
if(son[x])connect(son[x],anc);
for(int i=now[x];i;i=e[i].pre){
int cson=e[i].to;
if(cson!=fa[x]&&cson!=son[x])connect(cson,cson);
}
}
};
修改查询结点
直接在数据结构上面修改/查询就行了。注意要使用新的结点编号 。
修改查询路径
剖分完之后,每条重链就相当于一段
编号连续的区间。
整体修改或查询点
和点
的路径上的权值:
- 情况1:如果 和 在同一条重链上,直接用数据结构(代码中以线段树为例)修改/查询 至 这段区间的值。
- 情况2:如果
和
不在同一条重链上,一边进行修改/查询,一边将
和
往同一条重链上靠(当然是让起点深度大的往上跳),然后就变成了情况1。
- 情况A:若 与 在同一条重链上,修改/查询点 与 间的各权值,然后 跳至 ,就变成了情况1。
- 情况B:若 向上经过若干条重链和轻边后才与 在同一条重链上,不断地修改/查询当前 和 间的各权值,再将 跳至 ,直到 与 在同一条重链。
- 情况C:若 和 都是向上经过若干条重链和轻边,到达同一条重链,每次在点 和点 中,选择 较大的点 ,修改/查询 与 间的各权值,再跳至 ,直到点 和点 在同一条重链。
...
segment_tree segT;
struct tree{
edge e[maxn];
int now[maxn],edgtot=0,idtot=0;
int fa[maxn],son[maxn],dep[maxn],size[maxn],top[maxn];
...
int query(int x,int y){
int ans=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
ans+=segT.query(1,id[top[x]],id[x]);
x=fa[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
ans+=segT.query(1,id[x],id[y]);
return ans;
}
void modify(int x,int y,int d){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
segT.modify(1,id[top[x]],id[x],d);
x=fa[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
segT.modify(1,id[x],id[y],d);
}
};
修改查询子树
根据dfs序,同一棵子树上的结点的
编号也是连续的。表示这棵子树的区间就是以根节点
的编号
为起点、长度为
的区间。
...
segment_tree segT;
struct tree{
edge e[maxn];
int now[maxn],edgtot=0,idtot=0;
int fa[maxn],son[maxn],dep[maxn],size[maxn],top[maxn];
...
int query_subtree(int x){
return segT.query(1,id[x],id[x]+size[x]-1);
}
void modify_subtree(int x,int d){
segT.modify(1,id[x],id[x]+size[x]-1,d);
}
};
求LCA
若两个点处在同一条重链上,深度低的那个就是LCA。
如果不在同一条重链上,那么就跟前面的修改/查询操作一样,让两个点向上跳,直至它们在一条重链上。
...
segment_tree segT;
struct tree{
edge e[maxn];
int now[maxn],edgtot=0,idtot=0;
int fa[maxn],son[maxn],dep[maxn],size[maxn],top[maxn];
...
int Lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
x=fa[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
return x;
}
};