这几天跟lxn学了一下树链剖分(当然ljm小儿子是我的主讲老师啦,还是要感谢的),感觉挺简单的,怕忘在此记录一下。
首先,如果不会线段树,先移步去学一下吧……好了现在我们很熟悉线段树,那么故事开始了:
现在有个大佬走过来,命令你“在一棵树上进行路径的修改、求极值、求和”,你乍一听很高兴,上线段树(当然树状数组,SBT,splay都行啦。。。)!但操作起来你会发现,仅凭线段树是不能快速搞定它的。
怎么办呢?一个貌似很高级的算法出现了,定睛一看,竟是树链剖分。(感觉好傻逼啊~~)
所谓树链,就是树上的路径;剖分,就是把路径分为重链和轻链。
什么是重链和轻链呢?假如现在我们定义 son[v] 表示以v为根的子树的节点数,depth[v] 表示v在树中的深度(根节点的深度为1),那么就有一下定义:
定义科普:
重儿子:son[u] 为v的子节点中son值最大的,那么u就是v的重儿子;
轻儿子:v的其他子节点;
重边:点v与其重儿子的连边;
轻边:点v与其轻儿子的连边;
重链:由重边连成的路径;
轻链:轻边。
由此我们若定义 top[v] 表示v所在的链的顶端节点,father[v] 表示v的父亲,heavy_son[v] 表示与v在同一重链上的v的儿子节点(你可以称其为重儿子),id[v] 表示v与其父亲节点的连边(你可以称其为v的父边)在线段树中的位置。只要把这些东西都求出来,就能用log(n) 的时间完成原问题中的操作了。
一棵树如果被剖分了的话,会具有如下性质:
性质一:如果 (v,u) 为轻边,则 son[u] * 2 < son[v] ;
性质二:从根到某一节点的路径上轻链、重链的个数都不大于log(n) 。
其实就是每经过一条轻边,点的个数就至少除以二。假如不理解,还是劝您先记住为好吧。
有了这些定义和锐利的性质,我们就要开始实现算法了:
一、求出father、depth、son、heavy_son、top、id
我们采用两遍dfs 的方式将它们们求出:
dfs_1:把father、depth、son、heavy_son 求出来,这一步很简单,看看代码就能懂啦;
dfs_2:求出top 和id ;
这一步怎么办呢?首先,对于v ,当heavy_son[v] 存在(即v不是叶子结点)时,显然,我们有top[heavy_son[v]] = top[v] 。线段树中,v的重边应当在v 的父边的后面,记做id[ heavy_son[v]] = totw + 1 , totw表示最后加入的一条边在线段树中的位置。此时为了使一条重链上的各边在线段树中连续分布,应当进行dfs_2(heavy_son[v])。其次,对于v 的各个轻儿子u ,显然有top[u] =u,并且id[u] = totw + 1,进行dfs_2 过程。这样就求出了top 和id ;
将树中各边/点(依题而定)的权值在线段树中更新,建链和建线段树的过程就完成咯。
二、修改操作
eg. 将树从u到v节点最短路径上所有节点的权值加上dis;
我想如果不用树链剖分,你可能会先求LCA,现在我们有了树剖,不妨这么办:
记tu = top[u] ,tv = top[v] ,当tu <> tv 时,不妨设depth[tu] >= depth[tv] ,那么就更新 tu 到 u 点的权值,并使 u = father[tu] ,tu = top[u] ;当 tu = tv 时,u与v在同一条重链上,若u与v不是同一点,就更新u到v路径上的点的权值,否则修改完成。重复上述操作,直至修改完成。(具体代码里有)
其他操作(如求极值,求和等)就类似啦!为了各位好理解,这里附上洛谷P3384的一道模板树链剖分代码:
#include
#include
#include
#include
#define hee for(int i=1;i<=n*2;i++) cout< real[id[i]] == i
struct Edge{
int to,next;
}edge[maxn];
struct Segment_Tree{
LL l,r,sum,tag;
}tree[maxn];
inline int getint(void){
int x=0,f=1;
char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=-1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x*f;
}
inline LL getll(void){
LL x=0,f=1;
char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=-1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x*f;
}
inline void add_edge(const int&u,const int&v){
edge[++cnte].next=head[u];
edge[cnte].to=v;
head[u]=cnte;
}
void DFS_1(int now,int fa){
father[now]=fa;
depth[now]=depth[father[now]]+1;
son[now]=1;
for(int i=head[now];i;i=edge[i].next)
if(edge[i].to!=fa){
DFS_1(edge[i].to,now);
son[now]+=son[edge[i].to];
if(!heavy_son[now]||son[edge[i].to]>son[heavy_son[now]])
heavy_son[now]=edge[i].to;
}
}
void DFS_2(int now,int first){
top[now]=first;
id[now]=++temp;
real[id[now]]=now;
if(!heavy_son[now]) return ;
DFS_2(heavy_son[now],first);
for(int i=head[now];i;i=edge[i].next)
if(edge[i].to!=heavy_son[now]&&edge[i].to!=father[now])
DFS_2(edge[i].to,edge[i].to);
}
inline void push_up(int now){
tree[now].sum=tree[now<<1].sum+tree[now<<1|1].sum;
}
inline void push_down(int now){
tree[now<<1].tag+=tree[now].tag;
tree[now<<1].sum+=tree[now].tag*(tree[now<<1].r-tree[now<<1].l+1);
tree[now<<1|1].tag+=tree[now].tag;
tree[now<<1|1].sum+=tree[now].tag*(tree[now<<1|1].r-tree[now<<1|1].l+1);
tree[now].tag=0;
}
void build(int now,int l,int r){
tree[now].l=l;
tree[now].r=r;
if(l==r){ tree[now].sum=a[real[l]]; return ; }
int mid=(tree[now].l+tree[now].r)>>1;
build(now<<1,l,mid);
build(now<<1|1,mid+1,r);
push_up(now);
}
void update(int now,int ll,int rr,LL x){
if(ll<=tree[now].l&&tree[now].r<=rr){
tree[now].tag+=x;
tree[now].sum+=x*(tree[now].r-tree[now].l+1);
return ;
}
push_down(now);
int mid=(tree[now].l+tree[now].r)>>1;
if(rr<=mid) update(now<<1,ll,rr,x);
else if(ll>mid) update(now<<1|1,ll,rr,x);
else{
update(now<<1,ll,mid,x);
update(now<<1|1,mid+1,rr,x);
}
push_up(now);
}
LL query_sum(int now,int ll,int rr){
if(ll<=tree[now].l&&tree[now].r<=rr) return tree[now].sum;
push_down(now);
int mid=(tree[now].l+tree[now].r)>>1;
if(rr<=mid) return query_sum(now<<1,ll,rr);
else if(ll>mid) return query_sum(now<<1|1,ll,rr);
else return query_sum(now<<1,ll,mid)+query_sum(now<<1|1,mid+1,rr);
}
void change(int u,int v,LL x){
int tu=top[u],tv=top[v];
while(tu!=tv){
if(depth[tu]depth[v]) swap(u,v);
update(1,id[u],id[v],x);
}
int find_sum(int u,int v){
LL sum=0; int tu=top[u],tv=top[v];
while(tu!=tv){
if(depth[tu]depth[v]) swap(u,v); sum+=query_sum(1,id[u],id[v]);
return sum%=mod;
}
void root_add(int now,LL x){
int begin=id[now];
int end=id[now]+son[now]-1;
update(1,begin,end,x);
}
LL root_sum(int now){
int begin=id[now];
int end=id[now]+son[now]-1;
return query_sum(1,begin,end)%mod;
}
int main(int argc,char const*argv[]){
n=getint(); m=getint(); r=getint(); mod=getll();
for(int i=1;i<=n;i++) a[i]=getint();
for(int i=1;i<=n-1;i++){
int u=getint(),v=getint();
add_edge(u,v); add_edge(v,u);
}
DFS_1(r,0); DFS_2(r,r); build(1,1,temp);
for(int i=0;i
总之树剖就是这么简单啦,还有一些题,如树的统计(洛谷)等模板题,各位自己去刷吧。最后还是要谢谢ljm儿子的亲情讲解,看来还是养个儿用处多啊!