//以下定义及说明,部分来自不知名神犇的资料,侵删
定义
1.定义:
树链剖分*,计算机术语,指一种对树进行划分的算法,它先通过轻重边剖分将树分为多条链,保证每个点属于且只属于一条链,然后再通过数据结构(树状数组、SBT、SPLAY、线段树等)来维护每一条链。—-摘自百度百科
树链,就是树上的路径。剖分,就是把路径分类为重链和轻链。
每一个节点都属于且仅属于一条重链;
一般用线段树来维护,维护的是点权;
如果要维护边权,可以将边权赋给to点,根节点赋极小值。
注意求v–u路径长度时,v节点的值是fa[v]–v的边权,不应包含在内;
2.解释
变量解释:
sz[i] : 以i为根节点的子树的节点数,包括i节点本身。
son[i] : i节点的重儿子
inseg[i] : 原树内i号节点在线段树内的编号
intr[i] : 线段树内i号节点在原树内的编号
top[i] : i节点所在重链的头节点
cnt_seg : 线段树节点编号计数器
名词解释:
重儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子。
轻儿子:v的其它子节点。
重边:点v与其重儿子的连边。
轻边:点v与其轻儿子的连边。
重链:由重边连成的路径。
轻链:轻边
3.性质:
性质1:如果(v,u)为轻边,则siz[u] * 2 < siz[v];(反证,若大于,则必为重边);
性质2:从根到某一点的路径上轻边、重链的个数都不大于logn。
算法流程
重链剖分:
dfs1() 找重边,求出fa,deep,son,sz
void dfs1(int x,int fx){
fa[x]=fx;deep[x]=deep[fx]+1;
sz[x]=1;
for(int i=first[i];i;i=next[i]){
int v=e[i].t;
if(v==fx) continue;
dfs1(v,x);
sz[x]+=sz[v];
if(!son[x]||sz[son[x]]<sz[v])//记录重儿子
son[x]=v;
}
return ;
}
dfs2() 将重边连成重链
以根节点为起点,往下拉重链,不在当前重链上的节点,以其为根节点往下拉重链
1.对于重儿子,为了使一条重链各边在线段树中连续分布,应当进行dfs_2(son[v]);
2.对于轻儿子,以其本身重新拉重链。
void dfs2(int u,int tp){
top[u]=tp;
inseg[u]=++cnt_seg;
intr[cnt_seg]=u;
if(!son[u]) return ;//若为叶子节点,直接return
dfs2(son[u],tp);//重儿子
for(int i=first[u];i;i=next[i]){
int v=e[i].t;
if(v==fa[u]||v==son[u]) continue;
dfs2(v,v);//轻儿子
}
return ;
}
维护重链:
一条重链就是一段连续区间,将所有重链首尾相接,用线段树维护。
修改/查询:
修改查询操作类似,以修改为例。
单点修改
直接用线段树修改单点。
区间修改 (u,v)
1.u,v在一条重链上,直接用线段树修改区间(inseg[u],inseg[v])。
2.不在一条重链上,边修改边向一条重链上靠。
选择deep[top[]]大的点u,//防止跳过界
线段树修改区间(inseg[top[u]],inseg[u]);
将u跳至fa[top[u]],直至u,v在一条重链上,重复1。
注:
跳至同一条重链时,深度小的点即为lca,这就是树剖求lca,详见小机房的树。
蒋一瑶神犇的课件
https://wenku.baidu.com/view/a088de01eff9aef8941e06c3.html
模板:
题目:树的统计
#include<iostream>
#include<cstdio>
using namespace std;
const int N=50000+60,M=75000+60;
int n,m,a,b,c,tot,cnt_seg;
struct seg_tree{
int l,r,sum,maxx;
}seg[N<<2];
int e[M],first[N],next[M],fa[N],deep[N],top[N],inseg[N],intr[N],sz[N],son[N],val[N];
void build(int ff,int tt){
e[++tot]=tt;
next[tot]=first[ff];
first[ff]=tot;
return ;
}
void dfs1(int x,int fx){
fa[x]=fx;deep[x]=deep[fx]+1;
sz[x]=1;
for(int i=first[x];i;i=next[i]){
if(e[i]==fx) continue;
dfs1(e[i],x);
sz[x]+=sz[e[i]];
if(!son[x]||sz[son[x]]<sz[e[i]])
son[x]=e[i];
}
return ;
}
void dfs2(int t,int tp){
top[t]=tp;
inseg[t]=++cnt_seg;
intr[cnt_seg]=t;
if(!son[t]) return ;
dfs2(son[t],tp);
for(int i=first[t];i;i=next[i]){
int v=e[i];
if(v==fa[t]||v==son[t]) continue;
dfs2(v,v);
}
return ;
}
void updata(int num){
seg[num].maxx=max(seg[num<<1].maxx,seg[num<<1|1].maxx);
seg[num].sum=seg[num<<1].sum+seg[num<<1|1].sum;
return ;
}
void make_seg(int num,int ll,int rr){
seg[num].l=ll;seg[num].r=rr;
if(ll==rr){
seg[num].maxx=seg[num].sum=val[intr[ll]];
//易错。intr而不是inseg,线段树中ll点值是ll点在原树内的标号的值
return ;
}
int mid=ll+rr>>1;
make_seg(num<<1,ll,mid);
make_seg(num<<1|1,mid+1,rr);
updata(num);
return ;
}
int asksum_seg(int num,int ll,int rr){
if(ll>rr) swap(ll,rr);
if(seg[num].l>=ll&&seg[num].r<=rr) return seg[num].sum;
int mid=seg[num].l+seg[num].r>>1;
int ret=0;
if(ll<=mid) ret+=asksum_seg(num<<1,ll,rr);
if(rr>mid) ret+= asksum_seg(num<<1|1,ll,rr);
return ret;
}
int askmax_seg(int num,int ll,int rr){
if(ll>rr) swap(ll,rr);
if(seg[num].l>=ll&&seg[num].r<=rr) return seg[num].maxx;
int mid=seg[num].l+seg[num].r>>1;
int ret=-2e9;
if(ll<=mid) ret=max(ret,askmax_seg(num<<1,ll,rr));
if(rr>mid) ret=max(ret,askmax_seg(num<<1|1,ll,rr));
return ret;
}
int asksum(int x,int y){
int ans=0;
while(top[x]!=top[y]){
if(deep[top[y]]>deep[top[x]])//比较top的深度,而非xy的深度。
swap(x,y);
ans+=asksum_seg(1,inseg[top[x]],inseg[x]);
x=fa[top[x]];
}
ans+=asksum_seg(1,inseg[x],inseg[y]);
return ans;
}
int askmax(int x,int y){
int ans=-2e9;
while(top[x]!=top[y]){
if(deep[top[y]]>deep[top[x]])
swap(x,y);
ans=max(ans,askmax_seg(1,inseg[top[x]],inseg[x]));
x=fa[top[x]];
}
ans=max(ans,askmax_seg(1,inseg[x],inseg[y]));
return ans;
}
void change(int num,int x,int v){
if(seg[num].l==seg[num].r){
seg[num].sum=v;
seg[num].maxx=v;
return ;
}
int mid=seg[num].l+seg[num].r>>1;
if(x<=mid) change(num<<1,x,v);
else change(num<<1|1,x,v);
updata(num);
return ;
}
int main(){
scanf("%d",&n);
for(int i=1;i<n;i++){
scanf("%d%d",&a,&b);
build(a,b);build(b,a);
}
for(int i=1;i<=n;i++) scanf("%d",&val[i]);
dfs1(1,0);
dfs2(1,1);
make_seg(1,1,cnt_seg);
scanf("%d",&m);
for(int i=1;i<=m;i++){
string s;
cin>>s;
scanf("%d%d",&a,&b);
if(s=="CHANGE") change(1,inseg[a],b);
if(s=="QMAX") printf("%d\n",askmax(a,b));
//易错,这里要写a,b,而非inseg[a],inseg[b],因为重链在原树内,而不是在线段树内,线段树内只有多段连续的区间。
if(s=="QSUM") printf("%d\n",asksum(a,b));//易错,同上
}
return 0;
}