dfs序+树链剖分,超详细讲解+原理分析+模板(看不懂来打我
dfs序+树链剖分
一. 树链剖分能解决什么问题?
二.树链剖分前置知识
1.dfs序
2. 时间戳
按照dfs第一次访问的顺序,给每一个节点标记上时间戳
3.dfs序和时间戳有什么用处
开始剖分,跑第二次dfs的作用,也即dfs1和dfs2
于是你需要这些数组来记录
struct Edge{
int v;//节点编号
int next;
}edge[Maxn*2];//注意开二倍,因为是无相边
int head[Maxn];
int fa[Maxn];//记录父节点
int dep[Maxn];//记录节点深度
int son[Maxn];//记录重儿子
int siz[Maxn];//记录以该节点为根节点的树的大小(节点个数包括根节点)
int top[Maxn];//每一个节点所属重链的根节点
int dfn[Maxn];//每一个节点的时间戳
int w[Maxn];//dfs序后节点的权值,用线段树维护
int tim = 0;//时间戳计数器
int cnt = 0;
int v[Maxn];//存放所有节点的权值
int sum[Maxn*4];//线段树区间数组维护w[]区间和
int lazy[Maxn*4];//维护区间加的延迟数据,以便于延迟下方
int lpos[Maxn*4],rpos[Maxn*4];//线段树区间的左右端点
链式前向星存图:
/*链式前向星建图,无向边*/
void build(int u,int v){
edge[++cnt].v = v;
edge[cnt].next = head[u];
head[u] = cnt;
edge[++cnt].v = u;
edge[cnt].next = head[v];
head[v] = cnt;
}
dfs1
/*报一遍dfs记录重儿子,节点深度,以及树的大小*/
void dfs1(int u,int f){
fa[u] = f;
dep[u] = dep[f]+1;
siz[u] = 1;
int maxsonsize = -1;//记录重儿子的大小
for(int i=head[u];i!=-1;i=edge[i].next){
int v=edge[i].v;//与u有边的节点
if(v==f) continue;//如果v时u的父亲,直接跳过
/*否则就是u的儿子,dfs下去*/
dfs1(v,u);
siz[u]+=siz[v];
/*更新u的重儿子*/
if(siz[v]>maxsonsize){
maxsonsize = siz[v];
son[u] = v;
}
}
}
dfs2
/*再跑一边dfs,完成树链剖分*/
void dfs2(int u,int t){
dfn[u] = ++tim;//dfs序
top[u] = t;//u所属重链的祖先节点
w[tim] = v[u];//dfs序后的节点权值
/*没有重儿子,代表时根节点,直接return*/
if(!son[u]) return ;
dfs2(son[u],t);
for(int i=head[u];i!=-1;i=edge[i].next){
int v = edge[i].v;
//如果v时u的父节点,或者v时u的重儿子(已经遍历过了)
if(v==fa[u]||v==son[u]) continue;
/*否则以该节点为新的重链的祖先,继续dfs序*/
else dfs2(v,v);
}
return ;
}
线段树的区间修改和查询,对链进行操作
操作1
操作2
和操作1有一点不同,把modif()改为query()即可
三.树链剖分练习题
1.模板题
AC代码:
/*轻重链剖分/树链剖分*/
#include<bits/stdc++.h>
#define ls dep<<1
#define rs dep<<1|1
using namespace std;
const int Maxn = 1e5+10;
int N,M,R,P;
struct Edge{
int v;//节点编号
int next;
}edge[Maxn*2];
int head[Maxn];
int fa[Maxn];//记录父节点
int dep[Maxn];//记录节点深度
int son[Maxn];//记录重儿子
int siz[Maxn];//记录以该节点为根节点的树的大小(节点个数包括根节点)
int top[Maxn];//每一个节点所属重链的根节点
int dfn[Maxn];//每一个节点的时间戳
int w[Maxn];//dfs序后节点的权值,用线段树维护
int sum[Maxn*4];//线段树区间数组维护w[]区间和
int lazy[Maxn*4];//维护区间加的延迟数据,以便于延迟下方
int lpos[Maxn*4],rpos[Maxn*4];//线段树区间的左右端点
int tim = 0;//时间戳计数器
int cnt = 0;
int v[Maxn];//存放所有节点的权值
/*链式前向星建图,双向边*/
void build(int u,int v){
edge[++cnt].v = v;
edge[cnt].next = head[u];
head[u] = cnt;
edge[++cnt].v = u;
edge[cnt].next = head[v];
head[v] = cnt;
}
/*报一遍dfs记录重儿子,深度,以及子树大小*/
void dfs1(int u,int f){
fa[u] = f;
dep[u] = dep[f]+1;
siz[u] = 1;
int maxsonsize = -1;//记录重儿子的大小
for(int i=head[u];i!=-1;i=edge[i].next){
int v=edge[i].v;//与u有边的节点
if(v==f) continue;//如果v时u的父亲,直接跳过
/*否则就是u的儿子,dfs下去*/
dfs1(v,u);
siz[u]+=siz[v];
/*更新u的重儿子*/
if(siz[v]>maxsonsize){
maxsonsize = siz[v];
son[u] = v;
}
}
}
/*再跑一边dfs,完成树链剖分*/
void dfs2(int u,int t){
dfn[u] = ++tim;//dfs序
top[u] = t;//u所属重链的祖先节点
w[tim] = v[u];//dfs序后的节点权值
/*没有重儿子,代表时根节点,直接return*/
if(!son[u]) return ;
dfs2(son[u],t);
for(int i=head[u];i!=-1;i=edge[i].next){
int v = edge[i].v;
//如果v时u的父节点,或者v时u的重儿子(已经遍历过了)
if(v==fa[u]||v==son[u]) continue;
/*否则以该节点为新的重链的祖先,继续dfs序*/
else dfs2(v,v);
}
return ;
}
/*更新区间和*/
void pushup(int dep){
sum[dep] = sum[ls]%P+sum[rs]%P;
sum[dep]%=P;
return ;
}
/*延迟更新,标记下放操作*/
void pushdown(int dep){
if(lazy[dep]){
lazy[rs]+=lazy[dep];
lazy[ls]+=lazy[dep];
lazy[rs]%=P;
lazy[ls]%=P;
sum[ls]+=lazy[dep]%P*(rpos[ls]-lpos[ls]+1)%P;
sum[rs]+=lazy[dep]%P*(rpos[rs]-lpos[rs]+1)%P;
sum[ls]%=P;
sum[rs]%=P;
lazy[dep] = 0;
}
return ;
}
/*建立线段树*/
void build_tree(int l,int r,int dep){
if(l==r){
sum[dep] = w[l]%P;
sum[dep]%=P;
lpos[dep]=rpos[dep]=l;
return ;
}
int mid = l+r>>1;
build_tree(l,mid,ls);
build_tree(mid+1,r,rs);
lpos[dep] = l;
rpos[dep] = r;
pushup(dep);//更新区间和
}
void modify(int l,int r,int ql,int qr,int dep,int val){
if(ql<=l&&r<=qr){
sum[dep]+=(r-l+1)%P*val%P;
sum[dep]%=P;
lazy[dep]+=val%P;
lazy[dep]%=P;
return ;
}
pushdown(dep);
int mid = l+r>>1;
if(ql<=mid) modify(l,mid,ql,qr,ls,val);
if(qr>mid) modify(mid+1,r,ql,qr,rs,val);
pushup(dep);
return ;
}
/*区间查询*/
int query(int l,int r,int ql,int qr,int dep){
if(ql<=l&&r<=qr){
return sum[dep]%P;
}
pushdown(dep);
int mid = l+r>>1;
int ans = 0;
if(ql<=mid) ans+=query(l,mid,ql,qr,ls);
if(qr>mid) ans+=query(mid+1,r,ql,qr,rs);
ans%=P;
return ans;
}
/*初始化函数*/
void init(){
memset(head,-1,sizeof(head));
return ;
}
/*操作1:x-y链上的所有节点加上z*/
void modifychain(int x,int y,int z){
z%=P;//因为答案要模P,所以先对z取模
/*节点x和y不在同一条重链上*/
while(top[x]!=top[y]){
/*先修改重链祖先更深的点,这样才能不断向上走合并为,
转移到一条重链上最后(有点抽象,画个图)*/
if(dep[top[x]]<dep[top[y]]) swap(x,y);
modify(1,N,dfn[top[x]],dfn[x],1,z);//区间修改
x = fa[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
modify(1,N,dfn[x],dfn[y],1,z);
return ;
}
/*操作2:树从 x 到 y 结点最短路径上所有节点的值之和*/
int querychain(int x,int y){
int ans = 0;
/*节点x和y不在同一条重链上*/
while(top[x]!=top[y]){
/*先修改重链祖先更深的点,这样才能不断向上走合并为,
转移到一条重链上最后(有点抽象,画个图)*/
if(dep[top[x]]<dep[top[y]]) swap(x,y);
ans+=query(1,N,dfn[top[x]],dfn[x],1);//区间修改
ans%=P;
x = fa[top[x]];
}
if(dep[x]>dep[y]) swap(x,y);
ans+=query(1,N,dfn[x],dfn[y],1);
ans%=P;
return ans;
}
/*操作3:以x为根节点的子数内所有节点值都加上z*/
void modifyson(int x,int z){
/*因为一个节点的子树上的时间戳一定大于该节点,并且连续*/
modify(1,N,dfn[x],dfn[x]+siz[x]-1,1,z);//线段树维护的w[]数组的区间修改
}
/*操作4:求以x为根节点的子数内所有节点值之和*/
int queryson(int x){
return query(1,N,dfn[x],dfn[x]+siz[x]-1,1)%P;
}
/*
void t_sum(int l,int r,int dep){
if(l==r){
cout<<sum[dep]<<' '<<dep<<'\n';
return ;
}
int mid = l+r>>1;
t_sum(l,mid,ls);
t_sum(mid+1,r,rs);
return ;
}*/
int main()
{
init();
cin>>N>>M>>R>>P;//表示树的结点个数、操作个数、根节点序号和取模数
for(int i=1;i<=N;i++) cin>>v[i];
for(int i=1;i<N;i++){
int u,v;
cin>>u>>v;
build(u,v);
}
dfs1(R,R);
dfs2(R,R);
build_tree(1,N,1);
for(int i=1;i<=M;i++){
int op;
cin>>op;
if(op==1){
int x,y,z;
cin>>x>>y>>z;
modifychain(x,y,z);
}
else if(op==2){
int x,y;
cin>>x>>y;
cout<<querychain(x,y)%P<<'\n';
}
else if(op==3){
int x,z;
cin>>x>>z;
modifyson(x,z);
}
else if(op==4){
int x;
cin>>x;
cout<<queryson(x)%P<<'\n';
}
}
return 0;
}
2.树链剖分求最近公共祖先LCA
AC代码:
#include<bits/stdc++.h>
using namespace std;
const int Maxn = 5e5+10;
struct Edge{
int v;
int next;
}edge[Maxn*2];
int head[Maxn];
int cnt = 0;
/*链式前向星建图*/
void build(int u,int v){
edge[++cnt].v=v;
edge[cnt].next=head[u];
head[u] = cnt;
edge[++cnt].v = u;
edge[cnt].next = head[v];
head[v] = cnt;
return ;
}
int fa[Maxn];//父节点
int son[Maxn];//重儿子
int siz[Maxn];//树的大小
int dep[Maxn];//节点深度
int dfn[Maxn];//节点的时间戳
int top[Maxn];//每一条重链的根节点
int tim;//时间戳计数器
int N,M,S;
//第一次dfs找出重儿子,节点深度,树的大小
void dfs1(int u,int f){
fa[u] = f;
dep[u] = dep[f]+1;
siz[u] = 1;
int maxsonsize = -1;
for(int i=head[u];i!=-1;i=edge[i].next){
int v = edge[i].v;
/*如果v是u的父节点,continue*/
if(v==f) continue;
/*否则就是u的儿子*/
dfs1(v,u);
siz[u]+=siz[v];//更新u节点的大小
/*更新u节点的重儿子*/
if(siz[v]>maxsonsize){
maxsonsize = siz[v];
son[u] = v;
}
}
return ;
}
/*跑第二遍dfs,完成树链剖分,轻重链剖分
找出节点时间戳,重链的祖先节点*/
void dfs2(int u,int t){
dfn[u] = ++tim;
top[u] = t;
//cout<<u<<' '<<t<<'\n';
/*u没有重儿子*/
if(!son[u]) return ;
dfs2(son[u],t);
for(int i=head[u];i!=-1;i=edge[i].next){
int v = edge[i].v;
//cout<<u<<' '<<v<<'\n';
/*如果v是u的父亲continue,如果v是u的重儿子(已经dfs过continue)*/
if(v==fa[u]||v==son[u]) continue;
/*否则v是u的轻儿子之一,以这个节点作为新重链的祖先节点*/
dfs2(v,v);
}
return ;
}
/*找x和y的最近公共祖先*/
void solve(int x,int y){
/*不在一条重链上*/
while(top[x]!=top[y]){
/*找到深的一条链的祖先*/
if(dep[top[x]]<dep[top[y]]) swap(x,y);
x=fa[top[x]];
}
/*在一条重链上之后,谁知在上面谁是最近公共祖先*/
if(dep[x]<dep[y]) cout<<x;
else cout<<y;
cout<<'\n';
return ;
}
void init(){
memset(head,-1,sizeof(head));
return ;
}
int main(){
init();
cin>>N>>M>>S;//分别表示树的结点个数、询问的个数和树根结点的序号
for(int i=1;i<N;i++){
int u,v;
cin>>u>>v;
build(u,v);
}
//cout<<1<<'\n';
dfs1(S,S);
dfs2(S,S);
//cout<<top[2];
//cout<<1<<'\n';
for(int i=1;i<=M;i++){
int x,y;
cin>>x>>y;
solve(x,y);
}
return 0;
}
四.视频资源讲解
本博客采用了某站博主的视频页面,如有冒犯请联系本人,谢谢!