前言
先通过轻重边剖分将树分为多条链,保证每个点属于且只属于一条链,然后再通过数据结构来维护每一条链,许多比赛都会用到这个方法。
相关概念
重结点:子树结点数目最多的结点;
轻节点:父亲节点中除了重结点以外的结点;
重边:父亲结点和重结点连成的边;
轻边:父亲节点和轻节点连成的边;
重链:由多条重边连接而成的路径;
轻链:由多条轻边连接而成的路径;
相关数组定义
s[x] 保存以x为根的子树节点个数
top[x] 保存当前节点所在链的顶端节点
son[x] 保存重儿子
dep[x] 保存结点x的深度值
fa[x] 保存结点x的父亲节点
id[x] 保存树中每个节点剖分以后的新编号(DFS的执行顺序)
rank[x] 保存当前节点在树中的位置
相关性质
1、ž轻边(U,V),size(V)<=size(U)/2。
2、ž从根到某一点的路径上,不超过O(logN)条轻边,不超过O(logN)条重路径。
因为性质2,树链剖分就有了一个很不错的时间复杂度。
具体实现
1、第一遍dfs求出所以节点的s,son,dep,fa。
void dfs(int x)
{
int t=0;si[x]=1;
for(int i=lst[x];i;i=nxt[i])
if(to[i]!=fa[x])
{
dep[to[i]]=dep[x]+1;
fa[to[i]]=x;
dfs(to[i]);
si[x]+=si[to[i]];
if(si[to[i]]>t)t=si[to[i]],son[x]=to[i];
}
}
2、第二遍dfs,
按照重儿子优先的顺序,跟所以点重新编号,并求出每个点所在重链的链顶。
void dfs_(int x,int t)
{
id[x]=++now;
rank[now]=x;
top[x]=t;
if(son[x])dfs_(son[x],t);
for(int i=lst[x];i;i=nxt[i])
if(to[i]!=fa[x] && to[i]!=son[x])
dfs_(to[i],to[i]);
}
两遍dfs之后,树链剖分就可了,剩下就是用数据结构去维护了。
在重新的编号中,重链是连续的一段。
查询两个点之间的信息时,先查找lca。
具体做法:
判断两个点是否在同一条重链上,如果是,lca就是在这条重链上了。
如果不是,选择深度(链顶)较大的重链向上跳一条重链(类似求lca)。
不断重复上面的操作,直到找到lca为止。
每向上跳一次,都记录下需要查询的信息。
因为从根到某一点的路径上,不超过O(logN)条轻边,不超过O(logN)条重路径,那么查询的复杂度就是
O(logn∗数据结构复杂度)