树链剖分
前言:
树链剖分实际上就是一种将树形结构剖分成一条条链状结构,并用线性数据结构来快速维护信息。
重链剖分:
一些定义:
-
重儿子:一个节点的重儿子定义为它的子节点中子树节点最大的节点。
-
轻儿子:一个节点除重儿子外的所有儿子
-
重边:一个节点到它的重儿子的边即为重边
-
轻边:一个节点到它的轻儿子的边即为轻边
-
重链:一条所有都由重边组成的链即为重链(特别的,一个落单的节点也算一条重链)
一些性质:
-
一棵树所有的节点都属于且仅属于一条重链,所有的重链将整棵树完全剖分
-
每次沿着一条轻边往下走,所在子树大小至少除以二。
-
对于每条路径,都可以剖分成不超过 \(\log n\) 条重链。
剖分过程:
先给出一些需要用到的变量定义:
\(fa[u]\): 表示节点 \(u\) 的父亲编号。
\(siz[u]\):表示节点 \(u\) 的子树大小。
\(dep[u]\): 表示节点 \(u\) 的深度。
\(son[u]\): 表示节点 \(u\) 的重儿子编号。
\(top[u]\): 表示节点 \(u\) 所在重链的顶端。
\(dfn[u]\):表示节点 \(u\) 的 \(\rm dfs\) 序,也是节点 \(u\) 在数据结构中的编号。
\(rev[u]\):表示 \(\rm dfs\) 序为 \(u\) 的节点编号,有 \(rev[dfn[u]]=u\)。
我们可以通过两次 \(\rm dfs\) 来求出这些信息。两次分别求出前四者和后三者。
第一次 \(\dfs\) 比较简单,给出代码:
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;
}
关键是第二次 \(\rm dfs\)。第二次 \(\rm dfs\) 需要保证重链中节点的 \(dfn\) 值连续。所以我们要优先走重儿子。细节见代码:
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] 树的统计
我们考虑对 \(x,y\) 两点求 LCA 的过程。
记 \(fx\) 是节点 \(x\) 的重链链顶,\(fy\) 同理。
此时我们选取两者链顶深度较大的向上跳到链顶的父亲处,并同时将这条链的贡献记入答案,显然由于 \(dfn\) 序连续,可以直接用线段树来维护。
本题代码如下:
#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;
}