树链剖分
前言:
树链剖分实际上就是一种将树形结构剖分成一条条链状结构,并用线性数据结构来快速维护信息。
重链剖分:
一些定义:
-
重儿子:一个节点的重儿子定义为它的子节点中子树节点最大的节点。
-
轻儿子:一个节点除重儿子外的所有儿子
-
重边:一个节点到它的重儿子的边即为重边
-
轻边:一个节点到它的轻儿子的边即为轻边
-
重链:一条所有都由重边组成的链即为重链(特别的,一个落单的节点也算一条重链)
一些性质:
-
一棵树所有的节点都属于且仅属于一条重链,所有的重链将整棵树完全剖分
-
每次沿着一条轻边往下走,所在子树大小至少除以二。
-
对于每条路径,都可以剖分成不超过
条重链。
剖分过程:
先给出一些需要用到的变量定义:
我们可以通过两次
第一次
void dfs1(int u,int father){ siz[u]=1; fa[u]=father; dep[u]=dep[father]+1; int maxsiz=0; for(int i=head[u];i;i=edges[i].next){ int v=edges[i].v; if(v==father)continue; dfs1(v,u); siz[u]+=siz[v]; if(siz[v]>maxsiz){ maxsiz=siz[v]; son[u]=v; } } return; }
关键是第二次
void dfs2(int u,int t){//t 是重链链顶 top[u]=t; dfn[u]=++dfncnt; rev[dfn[u]]=u; if(!son[u])return;// 注意这里 dfs2(son[u],t);// 因为走的是重边,所以链顶不变 for(int i=head[u];i;i=edges[i].next){ int v=edges[i].v; if(v==fa[u]||v==son[u])continue; dfs2(v,v);// 这里走的是轻边,导致链断开了 } return; }
此时我们就完成了重链剖分的过程了。
拿一道题来举例:P2590 [ZJOI2008] 树的统计
我们考虑对
记
此时我们选取两者链顶深度较大的向上跳到链顶的父亲处,并同时将这条链的贡献记入答案,显然由于
本题代码如下:
#include<bits/stdc++.h> #define int long long using namespace std; const int N=5e4+10,INF=0x3f3f3f3f; struct edge{ int v,next; }edges[N*2]; int head[N],idx; int n,w[N]; int siz[N],top[N],dep[N],son[N],fa[N],dfn[N],rev[N]; void add_edge(int u,int v){ idx++; edges[idx].v=v; edges[idx].next=head[u]; head[u]=idx; return; }// 链式前向星加边 struct Segment_tree{ #define ls (o<<1) #define rs (o<<1|1) #define mid (l+r>>1) int tsum[N<<2],tmax[N<<2]; void pushup(int o){ tsum[o]=tsum[ls]+tsum[rs]; tmax[o]=max(tmax[ls],tmax[rs]); return; } void build(int o,int l,int r){ if(l==r){ tsum[o]=tmax[o]=w[rev[l]]; return; } build(ls,l,mid); build(rs,mid+1,r); pushup(o); return; }// 建树 void modify(int o,int l,int r,int id,int k){ if(l==r){ tsum[o]=tmax[o]=k; return; } if(id<=mid)modify(ls,l,mid,id,k); else modify(rs,mid+1,r,id,k); pushup(o); return; }// 单点更新 int querymax(int o,int l,int r,int s,int t){ if(s<=l&&r<=t)return tmax[o]; int ret=-INF; if(s<=mid)ret=querymax(ls,l,mid,s,t); if(mid<t)ret=max(ret,querymax(rs,mid+1,r,s,t)); return ret; }// 求区间最大值 int querysum(int o,int l,int r,int s,int t){ if(s<=l&&r<=t)return tsum[o]; int ret=0; if(s<=mid)ret=querysum(ls,l,mid,s,t); if(mid<t)ret+=querysum(rs,mid+1,r,s,t); return ret; }// 求区间和 }tree; void dfs1(int u,int father){ siz[u]=1; fa[u]=father; dep[u]=dep[father]+1; int maxsiz=0; for(int i=head[u];i;i=edges[i].next){ int v=edges[i].v; if(v==father)continue; dfs1(v,u); siz[u]+=siz[v]; if(siz[v]>maxsiz){ maxsiz=siz[v]; son[u]=v; } } return; }// 第一次 dfs int dfncnt; void dfs2(int u,int t){ top[u]=t; dfn[u]=++dfncnt; rev[dfn[u]]=u; if(!son[u])return; dfs2(son[u],t); for(int i=head[u];i;i=edges[i].next){ int v=edges[i].v; if(v==fa[u]||v==son[u])continue; dfs2(v,v); } return; }// 第二次 dfs int Qsum(int x,int y){ int ret=0; int fx=top[x],fy=top[y]; while(fx!=fy){ if(dep[fx]>=dep[fy]){ ret+=tree.querysum(1,1,n,dfn[fx],dfn[x]);// 注意不要写反了 x=fa[fx];// 向上跳要多跳一个节点 } else{ ret+=tree.querysum(1,1,n,dfn[fy],dfn[y]); y=fa[fy]; } fx=top[x];fy=top[y];// 记得更新 } if(dfn[x]<dfn[y])ret+=tree.querysum(1,1,n,dfn[x],dfn[y]);// 别忘了最后还要加上两点间的部分链 else ret+=tree.querysum(1,1,n,dfn[y],dfn[x]); return ret; } int Qmax(int x,int y){ int ret=-INF; int fx=top[x],fy=top[y]; while(fx!=fy){ if(dep[fx]>=dep[fy]){ ret=max(ret,tree.querymax(1,1,n,dfn[fx],dfn[x])); x=fa[fx]; } else{ ret=max(ret,tree.querymax(1,1,n,dfn[fy],dfn[y])); y=fa[fy]; } fx=top[x];fy=top[y]; } if(dfn[x]<dfn[y])ret=max(ret,tree.querymax(1,1,n,dfn[x],dfn[y])); else ret=max(ret,tree.querymax(1,1,n,dfn[y],dfn[x])); return ret; } signed main(){ std::ios::sync_with_stdio(false); cin.tie(0);cout.tie(0); cin>>n; for(int i=1;i<n;i++){ int x,y; cin>>x>>y; add_edge(x,y); add_edge(y,x); } for(int i=1;i<=n;i++)cin>>w[i]; dfs1(1,0); dfs2(1,1); tree.build(1,1,n); int T;cin>>T; while(T--){ string opt; int x,y; cin>>opt>>x>>y; if(opt=="CHANGE")tree.modify(1,1,n,dfn[x],y); if(opt=="QSUM")cout<<Qsum(x,y)<<"\n"; if(opt=="QMAX")cout<<Qmax(x,y)<<"\n"; } return 0; }
本文作者:Little_corn
本文链接:https://www.cnblogs.com/little-corn/p/18157458
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步