最近一直在看熟练剖分,终于有点理解是怎么回事了,在网上看到这一篇博客还挺好的,就简单的改了改一些细节,树链剖分就是将一棵树按照一种规则分成若干条链,把每一条链看作是一个单位,用数据结构来维护这些链的值。
“在一棵树上进行路径的修改、求极值、求和”乍一看只要线段树就能轻松解决,实际上,仅凭线段树是不能搞定它的。我们需要用到一种貌似高级的复杂算法——树链剖分。
树链,就是树上的路径。剖分,就是把路径分类为重链和轻链记:
dep[v]
:表示
v
的深度(根节点深度为
siz[v]
: 表示以
v
为根的子树的节点数;
*w[v]
重儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子。
轻儿子:v的其它子节点。
重边:点v与其重儿子的连边。
轻边:点v与其轻儿子的连边。
重链:由重边连成的路径。
轻链:轻边。
剖分后的树有如下性质:
性质1:如果(v,u)为轻边,则siz[u] * 2 < siz[v];
性质2:从根到某一点的路径上轻链、重链的个数都不大于logn。
算法实现:
我们可以用两个
dfs
来求出
fa、dep、siz、son、top、w。
dfs_1:把
fa、dep、siz、son
求出来,比较简单,略过。
dfs_2: ⒈对于
v
,当
⒉对于v的各个轻儿子u,显然有
top[u]=u
,并且
w[u]=totw+1
,进行
dfs2
过程。
这就求出了
top
和
w
。
将树中各边的权值在线段树中更新,建链和建线段树的过程就完成了。
修改操作:例如将u到v的路径上每条边的权值都加上某值x。
一般人需要先求LCA,然后慢慢修改
记
f1=top[u],f2=top[v]
。
当
f1<>f2
时:不妨设
dep[f1]>=dep[f2]
,那么就更新
u
到
当
f1=f2
时:
u
与
重复上述过程,直到修改完成。
求和、求极值操作:类似修改操作,但是不更新边权,而是对其求和、求极值。
就这样,原问题就解决了。画图来看看:树链剖分
如吓图所示,较粗的为重边,较细的为轻边。节点编号旁边有个红色点的表明该节点是其所在链的顶端节点。边旁的蓝色数字表示该边在线段树中的位置。图中 1−4−9−13−14 为一条重链。
当要修改 11 到 10 的路径时。
第一次迭代:
u=11,v=10,f1=2,f2=10
。此时
dep[f1]<dep[f2]
,因此修改线段树中的
5
号点,
第二次迭代:
dep[f1]>dep[f2]
,修改线段树中10–11号点。
u=2,f1=2;
第三次迭代:
dep[f1]>dep[f2]
,修改线段树中
9
号点。
第四次迭代:
f1=f2
且
u=v
,修改结束。
在树链剖分的题型中有边权型和点权型之分,点权的既叫好理解,就不多说了。边权就是将边映射到深度较深的那个点上。
// 点权型 单点更新 区间查询
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
const int Vmax = 5*1e4 + 5;//点的数量
const int Emax =2*1e5+5;//边的数量 小于Vmax的两倍
namespace segment_tree{
int sum[Vmax<<2],add[Vmax<<2];
inline void pushup(int rt){
sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
void update(int L,int c,int l,int r,int rt=1){
if (L == l && l == r)
{
sum[rt] = c;
return ;
}
int m = (l + r) >> 1;
if (L <= m) update(L , c , lson);
else update(L , c , rson);
pushup(rt);
}
int query(int L,int R,int l,int r,int rt=1){
if (L <= l && r <= R)
return sum[rt];
int m = (l + r) >> 1;
int ret = 0;
if (L <= m) ret+=query(L , R , lson);
if (m < R) ret+=query(L , R , rson);
return ret;
}
}
namespace poufen{
using namespace segment_tree;
int siz[Vmax],son[Vmax],fa[Vmax],dep[Vmax],top[Vmax],w[Vmax];
int nodenum;
struct edge{
int v,next;
}e[Emax];
int pre[Vmax],ecnt;
inline void init(){
memset(pre, -1, sizeof(pre));
ecnt=0;
}
inline void add_(int u,int v){
e[ecnt].v=v;
e[ecnt].next=pre[u];
pre[u]=ecnt++;
}
void dfs(int u){
siz[u]=1;son[u]=0;//下标从1开始,son[0]初始为0
for(int i=pre[u];~i;i=e[i].next)
{
int v=e[i].v;
if(fa[u]!=v)
{
fa[v]=u;
dep[v]=dep[u]+1;//初始根节点dep!!
dfs(v);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]])son[u]=v;
}
}
}
void build_tree(int u,int tp){
top[u]=tp,w[u]=++nodenum;
if(son[u])build_tree(son[u],tp);
for(int i=pre[u];~i;i=e[i].next)
if(e[i].v!=fa[u]&&e[i].v!=son[u])
build_tree(e[i].v,e[i].v);
}
inline int find1(int u,int v){
int ret=0;
int f1=top[u],f2=top[v];
while(f1!=f2)
{
if(dep[f1]<dep[f2])
swap(f1,f2),swap(u,v);
ret+=query(w[f1],w[u],1,nodenum);
u=fa[f1];
f1=top[u];
}
if(dep[u]>dep[v])swap(u,v);
ret+=query(w[u],w[v],1,nodenum);
return ret;
}
int a[Vmax],b[Vmax];
int val[Vmax];//
void work1(int n)
{
memset(siz, 0, sizeof(siz));
memset(sum, 0, sizeof(sum));
init();
int root=1;
fa[root]=nodenum=dep[root]=0;
for(int i=1;i<=n;i++)
scanf("%d",&val[i]);
for(int i=1;i<n;i++)
{
scanf("%d%d",&a[i],&b[i]);
add_(a[i],b[i]);
add_(b[i],a[i]);
}
dfs(root);
build_tree(root,root);
for(int i=1;i<=n;i++)
update(w[i],val[i],1,nodenum);
}
}
using namespace poufen;