树链割分原理和实现
作者:互联网
树链割分即将树分割成为一段一段的线段区间,具体分割的过程就是一个dfs的过程,然后就可以将树结构划分为数组结构,从而实现树上的区间操作,即用线段树或者树状数组处理。
首先就是一些必须知道的概念:
- 重结点:子树结点数目最多的结点;
- 轻节点:父亲节点中除了重结点以外的结点;
- 重边:父亲结点和重结点连成的边;
- 轻边:父亲节点和轻节点连成的边;
- 重链:由多条重边连接而成的路径;
- 轻链:由多条轻边连接而成的路径;
fa[u]:保存结点u的父亲节点
dep[u]:保存结点u的深度值
siz[u]:保存以u为根的子树节点个数
son[u]:保存重儿子
rnk[u]:保存当前dfs标号在树中所对应的节点
top[u]:保存当前节点所在链的顶端节点
tid[u]:保存树中每个节点剖分以后的新编号(DFS的执行顺序)
树链剖分的两个性质:
1,如果(u, v)是一条轻边,那么size(v) < size(u)/2;
2,从根结点到任意结点的路所经过的轻重链的个数必定都小于logn;
可以证明,树链剖分的时间复杂度为O(nlog^2n)
点权的树链剖分
两次dfs用于获取带最长链的树链:
vector<int> tr[maxn];
int top[maxn];
int fa[maxn];
int dep[maxn];
int sz[maxn];
int son[maxn];
int tid[maxn]; //tree编号转dfs编号
int rnk[maxn]; //dfs编号转tree编号
int dfsnum;
void dfs1(int u,int father,int depth) //当前节点、父节点、层次深度
{
fa[u]=father;
dep[u]=depth;
sz[u]=1; //这个点本身size=1
int len=tr[u].size();
for(int i=0;i<len;i++)
{
int to=tr[u][i];
if(to==father)
continue;
dfs1(to,u,depth+1); //层次深度+1
sz[u]+=sz[to]; //子节点的size已被处理,用它来更新父节点的size
if(sz[to]>sz[son[u]])
son[u]=to; //选取size最大的作为重儿子
}
}
void dfs2(int u,int t) //当前节点、重链顶端
{
top[u]=t;
tid[u]=++dfsnum; //标记dfs序
rnk[dfsnum]=u; //序号cnt对应节点u
if(!son[u])
return;
dfs2(son[u],t);
/*我们选择优先进入重儿子来保证一条重链上各个节点dfs序连续,
一个点和它的重儿子处于同一条重链,所以重儿子所在重链的顶端还是t*/
int len=tr[u].size();
for(int i=0;i<len;i++)
{
int to=tr[u][i];
if(to!=son[u] && to!=fa[u])
dfs2(to,to); //一个点位于轻链底端,那么它的top必然是它本身
}
}
void treeSplit(int root)
{
memset(son,0,sizeof(son));
memset(sz,0,sizeof(sz));
dfsnum=0;
dfs1(root,-1,0);
dfs2(root,root);
}
由于对于点的存储vector只需要存储int,因此对于边的优化影响不大
边存储优化(完整的最优化树链剖分 点权)
struct edge
{
int to;
int next;
};
edge e[maxn<<1];
int tot;
int head[maxn];
void initTree()
{
tot=0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v)
{
e[tot].to=v;
e[tot].next=head[u];
head[u]=tot;
tot++;
}
int top[maxn];
int fa[maxn];
int dep[maxn];
int sz[maxn];
int son[maxn];
int tid[maxn]; //tree编号转dfs编号
int rnk[maxn]; //dfs编号转tree编号
int dfsnum;
void dfs1(int u,int father,int depth) //当前节点、父节点、层次深度
{
fa[u]=father;
dep[u]=depth;
sz[u]=1; //这个点本身size=1
for(int i=head[u];i!=-1;i=e[i].next)
{
int to=e[i].to;
if(to==father)
continue;
dfs1(to,u,depth+1); //层次深度+1
sz[u]+=sz[to]; //子节点的size已被处理,用它来更新父节点的size
if(sz[to]>sz[son[u]])
son[u]=to; //选取size最大的作为重儿子
}
}
void dfs2(int u,int t) //当前节点、重链顶端
{
top[u]=t;
tid[u]=++dfsnum; //标记dfs序
rnk[dfsnum]=u; //序号cnt对应节点u
if(!son[u])
return;
dfs2(son[u],t);
/*我们选择优先进入重儿子来保证一条重链上各个节点dfs序连续,
一个点和它的重儿子处于同一条重链,所以重儿子所在重链的顶端还是t*/
for(int i=head[u];i!=-1;i=e[i].next)
{
int to=e[i].to;
if(to!=son[u] && to!=fa[u])
dfs2(to,to); //一个点位于轻链底端,那么它的top必然是它本身
}
}
void treeSplit(int root)
{
memset(son,0,sizeof(son));
memset(sz,0,sizeof(sz));
dfsnum=0;
dfs1(root,-1,0);
dfs2(root,root);
}
如果不用需要最长链,可以修改成一次dfs(该方法会加长后续操作时间)
vector<int> tree[maxn];
int top[maxn];
int fa[maxn];
int dep[maxn];
int son[maxn];
int tid[maxn]; //tree编号转dfs编号
int rnk[maxn]; //dfs编号转tree编号
int dfsnum=0;
void init()
{
dfsnum=0;
memset(son,-1,sizeof(son));
}
//以上数组,并不是全部都需要,按照题目要求,可以用上述数组化简操作的时候才进行记录
void dfs(int u,int t,int father,int depth)
{
//u: 当前结点 t:该链的top结点 father: 父亲结点 depth: 深度
top[u]=t;
fa[u]=father;
dep[u]=depth;
tid[u]=++dfsnum;
rnk[tid[u]]=u;
bool first=true;
for(int i=0;i<tree[u].size();i++)
{
int to=tree[u][i];
if(to!=fa[u])
{
if(first)
{
son[u]=to;
dfs(to,t,u,depth + 1);
first=false;
}
else
dfs(to,to,u,depth + 1);
}
}
}
int main()
{
dfs(root,root,-1,1);
}
边权的树链剖分
int num[maxn];
struct edge
{
int to;
int w;
int num;
edge(){}
edge(int a,int b,int c){to=a;w=b;num=c;}
};
vector<edge> tr[maxn];
int eMapDfs[maxn];
int top[maxn];
int fa[maxn];
int dep[maxn];
int sz[maxn];
int son[maxn];
int tid[maxn]; //tree编号转dfs编号
int rnk[maxn]; //dfs编号转tree编号
int dfsnum;
void dfs1(int u,int father,int depth) //当前节点、父节点、层次深度
{
fa[u]=father;
dep[u]=depth;
sz[u]=1; //这个点本身size=1
int len=tr[u].size();
for(int i=0;i<len;i++)
{
int to=tr[u][i].to;
if(to==father)
continue;
num[to]=tr[u][i].w; //承认to是u的儿子了,将边权下放
eMapDfs[tr[u][i].num]=to; //当前边的编号对应的tree树的编号
dfs1(to,u,depth+1); //层次深度+1
sz[u]+=sz[to]; //子节点的size已被处理,用它来更新父节点的size
if(sz[to]>sz[son[u]])
son[u]=to; //选取size最大的作为重儿子
}
}
void dfs2(int u,int t) //当前节点、重链顶端
{
top[u]=t;
tid[u]=++dfsnum; //标记dfs序
rnk[dfsnum]=u; //序号cnt对应节点u
if(!son[u])
return;
dfs2(son[u],t);
/*我们选择优先进入重儿子来保证一条重链上各个节点dfs序连续,
一个点和它的重儿子处于同一条重链,所以重儿子所在重链的顶端还是t*/
int len=tr[u].size();
for(int i=0;i<len;i++)
{
int to=tr[u][i].to;
if(to!=son[u] && to!=fa[u])
dfs2(to,to); //一个点位于轻链底端,那么它的top必然是它本身
}
}
void treeSplit(int root)
{
memset(num,0,sizeof(num));
memset(son,0,sizeof(son));
memset(sz,0,sizeof(sz));
dfsnum=0;
dfs1(root,-1,0);
dfs2(root,root);
}
边vector的存储优化
(完整的最优化树链剖分 边权)
struct edge
{
int id;
int to;
int w;
int next;
};
edge e[maxn<<1];
int tot;
int head[maxn];
void initTree()
{
tot=0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int w,int id)
{
e[tot].to=v;
e[tot].next=head[u];
e[tot].id=id;
e[tot].w=w;
head[u]=tot;
tot++;
}
int num[maxn];
int eMapDfs[maxn]; //边序号转该边对应的权值下放的树节点编号
int top[maxn];
int fa[maxn];
int dep[maxn];
int sz[maxn]; //用于处理树链剖分的长链短链
int son[maxn];
int tid[maxn]; //tree编号转dfs编号
int rnk[maxn]; //dfs编号转tree编号
int dfsnum;
void dfs1(int u,int father,int depth) //当前节点、父节点、层次深度
{
fa[u]=father;
dep[u]=depth;
sz[u]=1; //这个点本身size=1
for(int i=head[u];i!=-1;i=e[i].next)
{ //当前边为e[i]
int to=e[i].to;
if(to==father)
continue;
num[to]=e[i].w;
eMapDfs[e[i].id]=to;
dfs1(to,u,depth+1); //层次深度+1
sz[u]+=sz[to]; //子节点的size已被处理,用它来更新父节点的size
if(sz[to]>sz[son[u]])
son[u]=to; //选取size最大的作为重儿子
}
}
void dfs2(int u,int t) //当前节点、重链顶端
{
top[u]=t;
tid[u]=++dfsnum; //标记dfs序
rnk[dfsnum]=u; //序号cnt对应节点u
if(!son[u])
return;
dfs2(son[u],t);
/*我们选择优先进入重儿子来保证一条重链上各个节点dfs序连续,
一个点和它的重儿子处于同一条重链,所以重儿子所在重链的顶端还是t*/
for(int i=head[u];i!=-1;i=e[i].next)
{
int to=e[i].to;
if(to!=son[u] && to!=fa[u])
dfs2(to,to); //一个点位于轻链底端,那么它的top必然是它本身
}
}
void treeSplit(int root)
{
memset(num,0,sizeof(num));
memset(son,0,sizeof(son));
memset(sz,0,sizeof(sz));
dfsnum=0;
dfs1(root,-1,0);
dfs2(root,root);
}
当树链构造成功之后,我们就可以利用线段树来维护树上的信息,来处理某点子树和到根路径的信息。
通过dfs我们已经保证一条重链上各个节点dfs序连续,那么可以想到,我们可以通过数据结构(以线段树为例)来维护一条重链的信息
回顾上文的那个题目,修改和查询操作原理是类似的,以查询操作为例,其实就是个LCA,不过这里使用了top来进行加速,因为top可以直接跳转到该重链的起始结点,轻链没有起始结点之说,他们的top就是自己。需要注意的是,每次循环只能跳一次,并且让结点深的那个来跳到top的位置,避免两个一起跳从而插肩而过。
树链构建线段树:
❤注意:由于线段树是跟随dfs序号构建的,因此需要用tid将树编号转化成dfs编号(下例)❤
ll sum[maxn<<2]; //随其他修改
void build(int l,int r,int pos) //k代表了挂该数据存储位置在第几个数组中,l,r代表该位置数组覆盖范围
{
if(l==r)
{
sum[pos]=num[rnk[l]]; //dfs编号转换成tree编号,在转换成树上val值
return;
}
int mid=(l+r)/2;
build(l,mid,pos<<1);
build(mid+1,r,pos<<1|1);
sum[pos]=sum[pos<<1]+sum[pos<<1|1];
}
当线段树构建完成后,就可以在线段树上进行一系列的修改与查询过程了。具体的修改与查询与具体的操作有关
图与树的dfs:https://blog.csdn.net/qq_38890926/article/details/81222698
树链路径
求取结点u到v路径上链的值(值在点上)
ll getTreeLine(int u,int v) //传入树编号
{
ll ans=0;
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
ans=ans+query(1,n,1,tid[top[u]],tid[u]);
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
ans=ans+query(1,n,1,tid[u],tid[v]);
return ans;
}
求取结点u到v路径上链的值(值在边上)
ll getTreeLine(int u,int v) //传入树编号
{
ll ans=0;
while(top[u]!=top[v])
{
if(dep[top[u]]<dep[top[v]]) swap(u,v);
ans=ans+query(1,n,1,tid[top[u]],tid[u]);
u=fa[top[u]];
}
if(dep[u]>dep[v]) swap(u,v);
ans=ans+query(1,n,1,tid[u]+1,tid[v]);
return ans;
}
点权,单点修改区间查询
#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<string>
#include<vector>
#include<cstdlib>
#include<cmath>
#include<set>
#include<map>
#include<stack>
#include<iomanip>
#include<cstring>
#include<sstream>
#include<iomanip>
#include<fstream>
#include<fstream>
#define maxn 100005
#define maxm 105
typedef long long ll;
const int inf=1e8+7;
const ll mod=1000000007;
const double eps=1e-10;
using namespace std;
inline void read(int &x){scanf("%d",&x);}
inline void readll(ll &x){scanf("%lld",&x);}
int n;
struct edge
{
int to;
int next;
};
edge e[maxn<<1];
int tot;
int head[maxn];
void init()
{
tot=0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v)
{
e[tot].to=v;
e[tot].next=head[u];
head[u]=tot;
tot++;
}
int a[maxn];
struct TreeSplit
{
int tp[maxn],fa[maxn],d[maxn],son[maxn];//关心链上信息
int tid[maxn],rnk[maxn];//tr转dfs,dfs转tr
int dfn,sz[maxn];
void dfs1(int u,int f,int dep)
{
fa[u]=f;d[u]=dep;sz[u]=1;son[u]=0;
for(int i=head[u];i!=-1;i=e[i].next)
{
int to=e[i].to;if(to==f)continue;
dfs1(to,u,dep+1);
sz[u]=sz[u]+sz[to];
if(sz[to]>sz[son[u]])son[u]=to;//重儿子size最大
}
}
void dfs2(int u,int t)//当前点,重链顶
{
tp[u]=t;tid[u]=++dfn;rnk[dfn]=u;
if(!son[u])return;
dfs2(son[u],t);//先重儿子保证重链dfs序连续
for(int i=head[u];i!=-1;i=e[i].next)
{
int to=e[i].to;
if(to!=son[u]&&to!=fa[u])dfs2(to,to);//点在轻链底端,top是它自己
}
}
void treeSplit(int rt)
{
dfn=0;
dfs1(rt,-1,0);dfs2(rt,rt);
}
ll getTreeLinemax(int u,int v);
ll getTreeLinesum(int u,int v);
}tree;
struct node
{
int l,r;
ll sum;
ll maxnum;
};
struct SegmentTree //单点修改,区间查询
{
node tr[maxn<<2];
inline void buildInit(int p)
{
tr[p].maxnum=tr[p].sum=a[tree.rnk[tr[p].l]];
}
inline void push_up(int p)
{
tr[p].sum=tr[p<<1].sum+tr[p<<1|1].sum;
tr[p].maxnum=max(tr[p<<1].maxnum,tr[p<<1|1].maxnum);
}
inline void changeOp(int p,ll v)
{
tr[p].maxnum=tr[p].sum=v;
}
inline node queryUnion(node a,node b)
{
node tmp;
tmp.sum=a.sum+b.sum;
tmp.maxnum=max(a.maxnum,b.maxnum);
return tmp;
}
void build(int l,int r,int p)
{
tr[p].l=l,tr[p].r=r;
if(l==r){buildInit(p);return;}
int mid=(l+r)>>1;
build(l,mid,p<<1);build(mid+1,r,p<<1|1);
push_up(p);
}
void change(int p,int t,ll v)
{
if(tr[p].l==tr[p].r){changeOp(p,v);return;}
int mid=(tr[p].l+tr[p].r)>>1;
if(t<=mid) change(p<<1,t,v);
else change(p<<1|1,t,v);
push_up(p);
}
node query(int p,int lt,int rt)
{
if(tr[p].l>=lt && tr[p].r<=rt){return tr[p];}
int mid=(tr[p].l+tr[p].r)>>1;
if(rt<=mid) return query(p<<1,lt,rt);
if(lt>mid) return query(p<<1|1,lt,rt);
return queryUnion(query(p<<1,lt,rt),query(p<<1|1,lt,rt));
}
}st;
ll TreeSplit::getTreeLinemax(int u,int v) //传入树编号
{
ll ans=-inf;
while(tp[u]!=tp[v])
{
if(d[tp[u]]<d[tp[v]])swap(u,v);
ans=max(ans,st.query(1,tid[tp[u]],tid[u]).maxnum);
u=fa[tp[u]];
}
if(d[u]<d[v])swap(u,v);
ans=max(ans,st.query(1,tid[v],tid[u]).maxnum);
return ans;
}
ll TreeSplit::getTreeLinesum(int u,int v) //传入树编号
{
ll ans=0;
while(tp[u]!=tp[v])
{
if(d[tp[u]]<d[tp[v]])swap(u,v);
ans=ans+st.query(1,tid[tp[u]],tid[u]).sum;
u=fa[tp[u]];
}
if(d[u]<d[v])swap(u,v);
ans=ans+st.query(1,tid[v],tid[u]).sum;
return ans;
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
init();
for(int i=1;i<n;i++)
{
int u,v;scanf("%d %d",&u,&v);
addedge(u,v);addedge(v,u);
}
tree.treeSplit(1);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
st.build(1,n,1);
int m;scanf("%d",&m);
for(int i=1;i<=m;i++)
{
char op[10];int x,y;scanf("%s %d %d",op,&x,&y);
if(op[3]=='X')printf("%lld\n",tree.getTreeLinemax(x,y));
if(op[3]=='M')printf("%lld\n",tree.getTreeLinesum(x,y));
if(op[3]=='N')st.change(1,tree.tid[x],y);
}
}
return 0;
}
边权,区间查询
#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
#include<string>
#include<vector>
#include<cstdlib>
#include<cmath>
#include<set>
#include<map>
#include<stack>
#include<iomanip>
#include<cstring>
#include<sstream>
#include<iomanip>
#include<fstream>
#include<fstream>
#define maxn 100005
#define maxm 105
typedef long long ll;
const int inf=1e9+7;
const ll mod=1000000007;
const double eps=1e-10;
using namespace std;
inline void read(int &x){scanf("%d",&x);}
inline void readll(ll &x){scanf("%lld",&x);}
int n,q;
struct edge
{
int to;
int w;
int next;
};
edge e[maxn<<1];
int tot;
int head[maxn];
void init()
{
tot=0;
memset(head,-1,sizeof(head));
}
void addedge(int u,int v,int w)
{
e[tot].to=v;
e[tot].w=w;
e[tot].next=head[u];
head[u]=tot;
tot++;
}
int a[maxn];
struct TreeSplit
{
int tp[maxn],fa[maxn],d[maxn],son[maxn];//关心链上信息
int tid[maxn],rnk[maxn];//tr转dfs,dfs转tr
int dfn,sz[maxn];
void dfs1(int u,int f,int dep)
{
fa[u]=f;d[u]=dep;sz[u]=1;son[u]=0;
for(int i=head[u];i!=-1;i=e[i].next)
{
int to=e[i].to;if(to==f)continue;
a[to]=e[i].w;
dfs1(to,u,dep+1);
sz[u]=sz[u]+sz[to];
if(sz[to]>sz[son[u]])son[u]=to;//重儿子size最大
}
}
void dfs2(int u,int t)//当前点,重链顶
{
tp[u]=t;tid[u]=++dfn;rnk[dfn]=u;
if(!son[u])return;
dfs2(son[u],t);//先重儿子保证重链dfs序连续
for(int i=head[u];i!=-1;i=e[i].next)
{
int to=e[i].to;
if(to!=son[u]&&to!=fa[u])dfs2(to,to);//点在轻链底端,top是它自己
}
}
void treeSplit(int rt)
{
dfn=0;
dfs1(rt,-1,0);dfs2(rt,rt);
}
ll getTreeLinemin(int u,int v);
}tree;
struct node
{
int l,r;
ll minnum;
node(){}
node(ll x){minnum=x;}
};
struct SegmentTree //单点修改,区间查询
{
node tr[maxn<<2];
inline void buildInit(int p)
{
tr[p].minnum=a[tree.rnk[tr[p].l]];
}
inline void push_up(int p)
{
tr[p].minnum=min(tr[p<<1].minnum,tr[p<<1|1].minnum);
}
inline node queryUnion(node a,node b)
{
node tmp;
tmp.minnum=min(a.minnum,b.minnum);
return tmp;
}
void build(int l,int r,int p)
{
tr[p].l=l,tr[p].r=r;
if(l==r){buildInit(p);return;}
int mid=(l+r)>>1;
build(l,mid,p<<1);build(mid+1,r,p<<1|1);
push_up(p);
}
node query(int p,int lt,int rt)
{
if(lt>rt)return node(inf);
if(tr[p].l>=lt && tr[p].r<=rt){return tr[p];}
int mid=(tr[p].l+tr[p].r)>>1;
if(rt<=mid) return query(p<<1,lt,rt);
if(lt>mid) return query(p<<1|1,lt,rt);
return queryUnion(query(p<<1,lt,rt),query(p<<1|1,lt,rt));
}
}st;
ll TreeSplit::getTreeLinemin(int u,int v) //传入树编号
{
ll ans=inf;
while(tp[u]!=tp[v])
{
if(d[tp[u]]<d[tp[v]])swap(u,v);
ans=min(ans,st.query(1,tid[tp[u]],tid[u]).minnum);
u=fa[tp[u]];
}
if(d[u]<d[v])swap(u,v);
ans=min(ans,st.query(1,tid[v]+1,tid[u]).minnum);
return ans;
}
int main()
{
cin>>n>>q;
init();
for(int i=1;i<n;i++)
{
int f,t,w;cin>>f>>t>>w;
addedge(f,t,w);addedge(t,f,w);
}
memset(a,0,sizeof(a));
tree.treeSplit(1);
st.build(1,n,1);
while(q--)
{
int f,t;cin>>f>>t;
cout<<tree.getTreeLinemin(f,t)<<endl;
}
return 0;
}
标签:重链,int,dfs,son,割分,maxn,树链,原理,include 来源: https://blog.csdn.net/qq_38890926/article/details/89380139