我们有时候需要一种方法快速地求出树上任意两点之间路径的权值和(或者路径上边权的最大最小值)。这时,我们就可以用到树链剖分。所谓树链剖分,就是将一棵树分为许多条链,然后将这些链连起来,用线段树来维护区间的和(或者最大最小值)。
下面定义几个术语及数组:
size[i]:以i为根结点的子树的节点个数
son[i]:i的子节点中size最大的子节点编号(若不存在则为-1)
重儿子:son[i]即为i的重儿子
轻儿子:i节点的其他子节点
重边:节点i与节点son[i]的边
轻边:节点i与其他子节点的边
重链:全部由重边组成的路径
dep[i]:节点i在树中的深度
fa[i]:节点i的父亲节点的编号(若不存在则为-1)
top[i]:节点i所在重链中深度最小的节点编号
w[i]:节点i的前驱边在线段树中的编号
为方便大家更形象地理解重链和轻边,这里给出一个例子:(其中粗线段为重链)
我们要使得每条重链在线段树中成为一段连续的区间,首先要对这棵树进行第一遍DFS,求出size[i]
,son[i]
,dep[i]
,fa[i]
,这相对来说比较简单,代码如下(使用邻接表,dep[i]初始化为-1,dep[1] = 0
,fa[1] = -1
),这里不再赘述:
void dfs1(int p) {
int i;
size[p] = 1;
for(i = 0; i < (int) G[p].size(); i++)
if(dep[G[p][i]] != -1) continue;
fa[G[p][i]] = p;
dep[G[p][i]] = dep[p]+1;
dfs1(G[p][i]);
size[p] += size[G[p][i]];
if(son[p]==-1 || size[G[p][i]]>size[son[p]]) son[p] = G[p][i];
}
}
然后我们再进行第二遍DFS,求出top[i]
和w[i]
。注意我们首先要对节点i的重儿子进行DFS,每一次往线段树种插入的边的编号依次叠加,才能使得重链上的边在线段树中是连续的区间。对于重儿子,top[son[p]] = top[p]
;对于轻儿子,top[G[p][i]] = G[p][i]
。代码如下:(注意e初始化为0,tree.update(p, l, r, x, v)
表示在[l,r]区间(线段树中节点p)内查找边x并将其值更新为v)
void dfs2(int p) {
int i;
if(son[p] != -1) {
top[son[p]] = top[p];
w[son[p]] = ++e;
tree.update(1, 1, n-1, e, W[p][son[p]]);
dfs2(son[p]);
}
for(i = 0; i < (int)G[p].size(); i++) {
if(G[p][i]==son[p] || G[p][i]==fa[p]) continue;
top[G[p][i]] = G[p][i];
w[G[p][i]] = ++e;
tree.update(1, 1, n-1, e, W[p][G[p][i]]);
dfs2(G[p][i]);
}
}
预处理已经完成,接下来就是查询了(当然也可以更改边的权值,但不可以增加或者删除边)。
查询操作以求路径上权值的权值和为例。(这一部分很多人都有使用LCA,但后来发现也可以不求LCA,直接求解。)
现在我们需要求点u,和点v之间路径上的边权和,令f1 = top[u]
,f2 = top[v]
。
若f1 == f2
,则说明u,v两点在同一条重链上,不妨设dep[u] < dep[v]
,则在线段树上查询区间[w[son[u]], w[v]]即可。
否则,则说明u,v不在同一条重链上,不妨设dep[f1] >= dep[f2]
,我们先查询区间[w[f1],w[u]](注:可以证明轻边w[f1]必为w[son[f1]]-1,所以这条轻边与这条重链组成一段连续区间,可以直接查询),然后u = fa[f1]
。
重复上面的步骤,直到u == v
,就可以了。
代码如下(tree.query(p, l, r, x, y)
表示在[l,r]区间(即线段树中节点p)内查询区间[x, y]):
int query(int u, int v) {
int res = 0;
int f1 = top[u], f2 = top[v];
while(f1 != f2) {
if(dep[f1] < dep[f2]) {
swap(f1, f2);
swap(u, v);
}
res = max(res, tree.query(1, 1, n-1, w[f1], w[u]));
u = fa[f1];
f1 = top[u];
}
if(dep[u] > dep[v]) swap(u, v);
if(u != v) res = max(res, tree.query(1, 1, n-1, w[son[u]], w[v]));
return res;
}
以上就是树链剖分的主要内容,下面是几道水题:
NOI2015day1t2 软件包管理器
SPOJ00375 QTREE - Query on a tree 题目传送门 题解传送门
POJ3237 Tree 题目传送门