像我这么懒肯定不会有图辣
在GCZ大爷的安利下来学树剖,慢慢补吧。。。
前言
这几天边看《高级数据结构》,边看各路神犇的Blog,终于看懂一点点了。不得不说那本书作者的码风是真的垃圾。
树链剖分,是一种将树剖分成多条不相交的链的算法,并通过其他的数据结构来维护这些链上的信息。
最简单的例子就是LCA,假设现在有一棵退化成链的树。如果要求任意两点的LCA,因为他们在同一条链上的缘故,只需要判断一下两者的深度就行了。由此可见,在链上是比在树上更好操作的。
那么该怎么将一棵树剖分开来捏?
先搬出一堆概念:
重儿子
在以\(X\)节点为根的子树中,节点数最多的子树的根节点,即是\(X\)节点的重儿子。不难证出,如果\(Y\)节点为\(X\)节点的重儿子,设\(size(cur)\)为以\(cur\)节点为根节点的子树的节点数,\(size(x)/2<=size(y)\)
重边
连接\(X\)节点与\(X\)节点的重儿子的边,我们叫他重边。
重链
一堆重边连起来的链。
轻链
一堆非重边连起来的链。
对于每个节点,找出其重儿子,剖分成一条条重链与轻链,就剖分好了。
基本操作
首先先要按照前面说的,\(dfs\)一遍树,找出每个点的重儿子。
inline void dfs1(int x,int fa)
{
depth[x]=depth[fa]+1; //x节点的深度等于其父节点的深度+1
father[x]=fa; //标记好x节点的父亲
siz[x]=1; //目前以x为根的子树的节点总数为1
int maxx=-1; //用来比较重儿子用
for(register int k=first_edge[x];k;k=edge[k].next_edge)
{
if(edge[k].to!=fa)
{
dfs1(edge[k].to,x);
siz[x]+=siz[edge[k].to]; //子树大小累计起来
if(siz[edge[k].to]>maxx) //更新重儿子的信息
{
maxx=siz[edge[k].to];
son[x]=edge[k].to;
}
}
}
return;
}
然后再\(dfs\)一遍树,这次要找出每条重链最上面的节点,顺便将每个点重新编号,保证一条链上的节点编号都是连续的,方便以后的维护。
inline void dfs2(int x,int topf)
{
dfn[x]=++number; //标记一下这个点的新编号
wt[number]=W[x]; //储存一下新的点权
top[x]=topf; //储存一下链最上面的顶点
if(!son[x]) return; //如果这个节点是叶子节点 直接返回
dfs2(son[x],topf); //优先处理重儿子 这样就保证了一条链上的节点编号是连续的
for(register int k=first_edge[x];k;k=edge[k].next_edge)
if(edge[k].to!=father[x] && edge[k].to!=son[x]) dfs2(edge[k].to,edge[k].to);
return;
}
上面两步做完之后,我们要对于这些链建一棵线段树。之前保证一条链上的节点编号是连续的,就是为了方便线段树的区间操作。
//你真的以为会有线段树的代码吗 naive
操作如下:
- 将树从\(X\)到$Y \(结点最短路径上所有节点的值都加上\)Z$
- 求树从\(X\)到\(Y\)结点最短路径上所有节点的值之和
- 将以\(X\)为根节点的子树内所有节点值都加上\(Z\)
- 求以\(x\)为根节点的子树内所有节点值之和
我们先来分析一下操作1。
很显然,\(X\)到\(Y\)的最短路径一定会经过他们的\(LCA\)。
前面也说了,如果\(X\)和\(Y\)在一条链上,谁深度小谁就是LCA,那么其实就是让\(X\)和\(Y\)不停的往上爬,直到爬到一条相同的链为止。然后再往上跳的过程中不断用线段树来更新就好了。
inline void update_range(int x,int y,int z)
{
z%=mod;
while(top[x]!=top[y]) //x和y不在一条链上
{
if(depth[top[x]]<depth[top[y]]) std::swap(x,y); //始终让x所在的链深度最大
segtree.update(1,1,N,idx[top[x]],idx[x],z); //用线段树更新
x=father[top[x]]; //跳到这条链的顶节点的父节点上面去
}
if(depth[x]>depth[y]) std::swap(x,y); //如果x的深度比y大 交换x和y 方便后面的操作
segtree.update(1,1,N,idx[x],idx[y],z); //此时在一条链上了 直接更新
return;
}
待填坑