文章目录
一.先知其用
树链剖分有两个用途:
①求树上节点x到节点y的路径上的点权和
②改变树上节点x到节点y的路径上所有点的点权
但是,这两个用途不都可以用LCA实现吗?
是的,但是我们要求的是在线的修改和查询,LCA这种东西除非你敢修改一次就重新建一次LCA,这时间一下子就飙上去了。
所以,这正是我们学树链剖分的意图。
二.预备知识
1.预备概念
重儿子:父亲结点的所有儿子结点中,子树拥有结点数最多的结点。
轻儿子:父亲结点除了重儿子以外的所有儿子。
重边:父亲结点与重儿子的连边。
轻边:父亲结点与轻儿子的连边。
重链:所有重边所组成的一条链。
2.变量声明
f[u]:保存点u的父亲结点
d[u]:保存点u的深度
size[u]:保存以u为根节点的子树的结点个数
son[u]:保存点u的重儿子
top[u]:保存点u所在重链的顶端结点
id[u]:保存点u进行重新编号后的新的编号
三.操作过程
这个其实不太难,但一定要把上面的概念理解清楚。
1.预处理
既然我们定义了这么多变量,定然是要一个一个处理出来的撒。
1.1.dfs1
首先,大家看,第一遍DFS能够处理出来那些数据:
定然是:f[u], d[u], size[u], son[u]
Code:
void dfs1 (int x, int fa, int dep){
//x是指此节点,fa是指父节点,dep是指当前深度
d[x] = dep;
siz[x] = 1;
f[x] = fa;
int maxs = -1;
for (int i = 0; i < G[x].size (); i ++){
int tmp = G[x][i];
if (tmp != fa){
dfs1 (tmp, x, dep + 1);
if (siz[tmp] > maxs)//建议大家像这样写,其他写法可能有bug
son[x] = tmp, maxs = siz[tmp];
siz[x] += siz[tmp];
}
}
}
1.2.dfs2
然后,看懂了第一个深搜,就来处理top[u], id[u]。
注意,我们处理出来之后,同一条重链上的点的id是从上到下递增的。
这就有点考技术,大家想一想,详见代码。
Code:
void dfs2 (int x, int fa){
//我这里id是用dfn代替的,这个fa其实并不代表父亲节点,而是代表重链的顶点
cnt ++;
top[x] = fa;
dfn[x] = cnt;
w[cnt] = val[x];
if (! son[x])//如果重链到头了,就返回
return ;
dfs2 (son[x], fa);
for (int i = 0; i < G[x].size (); i ++){
int tmp = G[x][i];
if (tmp != f[x] && tmp != son[x]){
//枚举下一条重链的开端
dfs2 (tmp, tmp);
}
}
}
2.开始操作
重头戏来了,现在,我们正式开始操作。
2.1.操作1:求节点x到节点y的路径上所有点权的总和
这里我们就要结合线段树了。
我们建一棵线段树出来(注意,按每个点的新编号进行建树)
然后,选节点x和节点y中深度较深的一个点出来进行爬树操作(爬到它那条重链的顶点的父亲节点)
然后一边爬一边通过线段树求和就行了。
爬到什么时候为止呢?
自然是两个节点已经到了同一条重链的时候结束。
最后再加上爬完树之后两点之间的点权和就行了。
其实挺好理解的。
Code
int get_sum (int x, int y){
int Sum = 0;
while (top[x] != top[y]){
if (d[top[x]] < d[top[y]])//让节点x成为深度最深的节点
swap (x, y);
Sum = (Sum + Query (1, dfn[top[x]], dfn[x])) % p;//就和
x = f[top[x]];//爬到重链顶尖的父节点
}
if (dfn[x] > dfn[y])//最后再加上爬完树之后两点之间的点权和
Sum = (Sum + Query (1, dfn[y], dfn[x])) % p;
else
Sum = (Sum + Query (1, dfn[x], dfn[y])) % p;
return Sum;
}
2.2.操作2:修改节点x到节点y的路径上所有点权
显而易见,就是把求和改成了修改,将上面的代码改一下就行了。
Code:
int Add (int x, int y, int z){
int Sum = 0;
while (top[x] != top[y]){
if (d[top[x]] < d[top[y]])
swap (x, y);
Update (1, dfn[top[x]], dfn[x], z);
x = f[top[x]];
}
if (d[x] > d[y])
swap (x, y);
Update (1, dfn[x], dfn[y], z);
}
3.线段树温馨提醒
如果线段树给打错了,肯定是十分恼火的。因为有时候这本是一道树链剖分的题,被你给做成了一道线段树的黑题。
错误一般都集中在建树和下传懒标记上,下面给出代码:
//建树
void build (int l, int r, int Index){
a[Index].L = l;
a[Index].R = r;
a[Index].lazy = a[Index].s = 0;
if (l != r){
int mid = (l + r) / 2;
build (l, mid, Index * 2);
build (mid + 1, r, Index * 2 + 1);
a[Index].s = (a[Index * 2].s + a[Index * 2 + 1].s