树链剖分
树链剖分
前言:虽然noip基本不考,但我觉得还是多少学一点吧
问题模型
对于树上的某条路径,进行一系列操作(类似线段树上的操作)
实现原理
恰如其名,将树剖分成一段又一段的区间(树链),便于我们进行线段树的操作(树上的线段树操作)。将树分为重链和轻链,重链的dfs序(与其他dfs序不同,树剖的dfs序会优先落在重链上)多为一段连续的区间。因此当解决两点间路径上的查询和修改问题时,可以用类似于LCA(其实区别较大,LCA是通过倍增来跳,而树剖是通过重链来跳)的方法,将两点之间的路径剖分成多条首尾相连的树链,每条树链上的点所在区间是连续的,进而进行线段树的操作。(关于重链,轻链的定义请自行查询)
代码实现
进行两遍dfs,第一遍储存size[],father[],son[](重儿子),dep[]
第二遍储存top[](所在重路径的顶部节点),seg[](在线段树中的位置),rev[](rev[seg[x]]=x)
void dfs1(int u,int father)
{
ssize[u]=1;
dep[u]=dep[father]+1;
fa[u]=father;
for(int i=fir[u];i;i=nxt[i])
{
int v=vv[i];
if(v==father)continue;
dfs1(v,u);
ssize[u]+=ssize[v];
if(ssize[v]>ssize[son[u]])son[u]=v;
}
}
/*
void dfs2(int u,int father)
{
if(son[u])
{
seg[son[u]]=++seg[0];
rev[seg[son[u]]]=son[u];
top[son[u]]=top[u];
dfs2(son[u],u);
}
for(int i=fir[u];i;i=nxt[i])
{
int v=vv[i];
if(!top[v])
{
seg[v]=++seg[0];
rev[seg[v]]=v;
top[v]=v;
dfs2(v,u);
}
}
}*/
**\(update\)于\(11\)月\(15\)日
最近了解到\(dfs2\)的第二种写法,不但码量少,而且可以不用在\(main\)函数里初始化\(root\)的\(dfs\)序
void dfs2(int u, int tp)
{
seg[u] = ++seg[0]; rev[seg[0]] = u; top[u] = tp;
if(son[u] != 0) dfs(son[u], tp);
for(int i = fir[u]; i; i = nxt[i])
{
int v = vv[i];
if(v == fa[u] || v == son[u]) continue;
dfs2(v, v);
}
}
**
当查询两点之间的路径时,通过每次top[]较深的点往上跳,当跳到同一条重链上时,深度较浅的一点即为两点的LCA,再将两点的路径转化为多条重链(跳跃的过程),分别进行区间查询。
void ask(int x,int y)
{
int fx=top[x],fy=top[y];
while(fx!=fy)
{
if(dep[fx]<dep[fy])swap(x,y),swap(fx,fy);
query(1,1,seg[0],seg[fx],seg[x]);
x=father[fx];fx=top[x];
}
if(dep[x]>dep[y])swap(x,y);
query(1,1,seg[0],seg[x],seg[y]);
}
例题
[Noi2015]软件包管理器
解析:虽然是NOI的题,但却是一道Day1 T2的除了题意比较冗长外的极水的树链剖分裸题。安装软件包就是将根节点到x节点的路径上所有值变为1,卸载软件包就是将x节点和它所有子树节点的值变为0,而x和它的子树节点在树链剖分中恰好是一段连续的区间[seg[x],seg[x]+size[x]-1],然后直接树链剖分做就可以了。