模版:
给定一棵树,有修改u,v节点路径上的信息,或修改 u 节点这颗子树的信息,或者查询链或 子树的信息 等操作。。。
可以一边操作一边查询。
日常解释用到的概念:
重儿子:父亲节点的所有儿子中子树结点数目最多(size最大)的结点;
轻儿子:父亲节点中除了重儿子以外的儿子;
重边:父亲结点和重儿子连成的边;
轻边:父亲节点和轻儿子连成的边;
重链:由多条重边连接而成的路径;
轻链:由多条轻边连接而成的路径;
故这里涉及到的数组有:
Hson[i] ( i 节点的重儿子是谁 )
dep[i] ( i 节点的深度)
Size[i] ( 以 i 为根节点的子树的大小 )
fa[i] ( i 节点的父亲是谁 )
dfsx[] ( 树的dfs序 )
rk[i] ( i 节点在 dfsx中的下标 )
top[i] ( i 节点所在的重链的顶节点是谁 )
所以首先需要一次dfs,处理 fa ,dep,Size,Hson.
紧接着是dfs2,处理 dfsx,rk,top数组。 在dfs2 中值得注意的是,先处理重儿子所在的链,保证重链的 dfs 序是连续的,这样就可以通过线段树来进行维护。
那么,对于一条链 就可以通过重链来跳跃优化,对于一个子树 它对应的 dfs 序区间就是 [ rk[i], rk[i] + Size[i] - 1 ],
贴上 dfs1 ,dfs2 的代码:
void dfs1(int p,int f){ /// 处理fa[],dep[],Size[],Hson[]
Size[p] = 1;
fa[p] = f;
dep[p] = dep[f] + 1;
for(int i = head[p]; ~i;i = edge[i].nex){
int v = edge[i].v;
if(v == f) continue;
dfs1(v,p);
Size[p] += Size[v];
if(Size[Hson[p]] < Size[v])
Hson[p] = v;
}
}
int xu = 0;
void dfs2(int p,int tp){ /// 处理dfs序,top[],dfsx[]
rk[p] = ++ xu;
top[p] = tp;
dfsx[xu] = p;
if(Hson[p]) ///保证重链的dfs序连续
dfs2(Hson[p],tp);
for(int i = head[p]; ~i;i = edge[i].nex){
int v = edge[i].v;
if(v != fa[p] && v != Hson[p])
dfs2(v,v); //一个点位于轻链底端,那么它的top必然是它本身
}
}
还有一点值得处理的地方,就是对于链的操作!!!
每一次对于链的操作,需要先使两个点在同一条重链上(跳的途中也有贡献),最后再来计算两个点之间的贡献。
这是点权:
/// 以线段树更新为例
void Function(int x,int y,int VAL){
while(top[x] != top[y]){
if(dep[top[x]] < dep[top[y]])
swap(x,y);
update(1,xu,rk[top[x]],rk[x],VAL,1); /// update(l,r,ul,ur,val,rt) . ul,ul:更新的区间
x = fa[top[x]];
}
if(rk[x] > rk[y])
swap(x,y);
update(1,xu,rk[x],rk[y],VAL,1);
}
这是边权:
如 u,v 之间有一条边, 边权为w.. 处理就是把 w 放在 u,v 中深度大的那个点。 这样就相当于每一条边的编号 都采用了 其下面那个点的编号。
也就相当于,每一个节点代表的是 节点 上方的那条边。那么就可以直接用这些点做树剖。
!!!但是这样更新的时候会有一个问题。。 假如当前 u 和 v 在同一条重链上,现在要查询 u 和 v 之间的边权,直接更新 是会多更新一条边的。 所以我们需要先对 深度小的那个点 走向其重儿子。
也就是这样:
/// 以线段树更新为例
void Function(int x,int y,int VAL){
while(top[x] != top[y]){
if(dep[top[x]] < dep[top[y]])
swap(x,y);
update(1,xu,rk[top[x]],rk[x],VAL,1); /// update(l,r,ul,ur,val,rt) . ul,ul:更新的区间
x = fa[top[x]];
}
if(x == y) return ; !!!与点权的区别
if(rk[x] > rk[y])
swap(x,y);
update(1,xu,rk[Hson[x]],rk[y],VAL,1); !!!!与点权的区别
}