浅谈树链剖分
基本概念
定义:将树中任意一条路径转化成 \(O(\log n)\) 段连续区间。
就是先把树转化成一条链,那么树中任意一条路径都可以转化成这条链中 \(O(\log n)\) 段连续区间,这样就可以把树上问题转化为区间的问题。
几个概念:
-
重儿子:对于一个非叶子节点 \(x\),先求一下它的每个子树的节点总数,那么 \(x\) 的重儿子就是节点数最多的那个子树的根节点。如果存在子树节点总数相同,那么可以任选一个点作为重儿子。
-
轻儿子:对于一个非叶子节点 \(x\),所有儿子中,不是重儿子的就是轻儿子。
-
重边:重儿子与它的父节点之间的边。
-
轻边:其它的边。
-
重链:极大的由重边构成的路径。如果这个点是重儿子,那么这个点就在它父节点的重链中,否则这个点就在从这个点开始向下走的重链中。
下图中,红色的点表示重儿子,红色的边表示重边,用蓝笔圈出表示重链:

我们可以按照这个树的 DFS 序将这个树变成序列。所谓 DFS 序,就是在 DFS 中,按顺序遍历到每个点的次序,顺序:优先遍历重儿子。性质:一条重链的编号是连续的,某一棵子树的编号也是连续的。
定理:数中任意一条路径均可拆分成 \(O(\log n)\) 条重链,即可拆分成 \(O(\log n)\) 个连续区间。
拆分方式:如果要拆分某两个点的路径,先看看它们所在的重链谁更矮,就跳到重链终点的父节点,再激活另一条重链,最后一定会跳到同一条重链上。
大概像这样:

例题
题目。
树链剖分后,直接用线段树。
参考代码:
#include<bits/stdc++.h>
#define mems(a,b) memset(a,b,sizeof a)
#define LL long long
using namespace std;
const int N=100010,M=200010;
int n,m,R,p;
int w[N],h[N],e[M],ne[M],idx;
int id[N],nw[N],cnt;//dfs序(新编号),新编号的权值
int dep[N],sz[N],top[N],fa[N],son[N];//深度,子树大小,重链顶点,父节点,重儿子
struct Tree{
int l,r;
LL add,sum;
}tr[N*4];
void add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
void dfs1(int u,int father,int depth){
dep[u]=depth;
fa[u]=father;
sz[u]=1;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==father) continue;
dfs1(j,u,depth+1);
sz[u]+=sz[j];
if(sz[son[u]]<sz[j]) son[u]=j;
}
}
void dfs2(int u,int t){//t表示当前点所在重链的顶点
id[u]=++cnt;
nw[cnt]=w[u];
top[u]=t;
if(!son[u]) return;//当前点是叶子节点
dfs2(son[u],t);//重儿子一定和自己在一条重链内
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==fa[u] || j==son[u]) continue;
dfs2(j,j);
}
}
void pushup(int u){
tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}
void pushdown(int u){
Tree &root=tr[u],&left=tr[u<<1],&right=tr[u<<1|1];
if(root.add){
left.add+=root.add;
left.sum+=root.add*(left.r-left.l+1);
right.add+=root.add;
right.sum+=root.add*(right.r-right.l+1);
root.add=0;
}
}
void build(int u,int l,int r){
tr[u]={l,r,0,nw[r]};
if(l==r) return;
int mid=l+r>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
pushup(u);
}
void update(int u,int l,int r,int k){
if(l<=tr[u].l && r>=tr[u].r){
tr[u].add+=k;
tr[u].sum+=k*(tr[u].r-tr[u].l+1);
return;
}
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
if(l<=mid) update(u<<1,l,r,k);
if(r>mid) update(u<<1|1,l,r,k);
pushup(u);
}
LL query(int u,int l,int r){
if(l<=tr[u].l && r>=tr[u].r) return tr[u].sum;
pushdown(u);
int mid=tr[u].l+tr[u].r>>1;
LL res=0;
if(l<=mid) res+=query(u<<1,l,r)%p;
if(r>mid) res+=query(u<<1|1,l,r)%p;
return res%p;
}
void update_path(int u,int v,int k){
while(top[u]!=top[v]){//不在同一条重链中
if(dep[top[u]]<dep[top[v]]) swap(u,v);//使top[v]在top[u]上面
update(1,id[top[u]],id[u],k);
u=fa[top[u]];
}
if(dep[u]<dep[v]) swap(u,v);//使v在u上面
update(1,id[v],id[u],k);
}
LL query_path(int u,int v){
LL res=0;
while(top[u]!=top[v]){//不在同一条重链中
if(dep[top[u]]<dep[top[v]]) swap(u,v);//使top[v]在top[u]上面
res+=query(1,id[top[u]],id[u])%p;
u=fa[top[u]];
}
if(dep[u]<dep[v]) swap(u,v);//使v在u上面
res+=query(1,id[v],id[u])%p;
return res%p;
}
void update_tree(int u,int k){
update(1,id[u],id[u]+sz[u]-1,k);
}
LL query_tree(int u){
return query(1,id[u],id[u]+sz[u]-1)%p;
}
int main(){
cin>>n>>m>>R>>p;
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
mems(h,-1);
for(int i=1;i<n;i++){
int a,b;
scanf("%d%d",&a,&b);
add(a,b);
add(b,a);
}
dfs1(R,-1,1);//求每个点的重儿子
dfs2(R,R);//求DFS序,根节点重链起点为自身
build(1,1,n);//建立线段树
while(m--){
int t,u,v,k;
scanf("%d%d",&t,&u);
if(t==1){
scanf("%d%d",&v,&k);
update_path(u,v,k);
}
else if(t==2){
scanf("%d",&v);
printf("%lld\n",query_path(u,v));
}
else if(t==3){
scanf("%d",&k);
update_tree(u,k);
}
else printf("%lld\n",query_tree(u));
}
return 0;
}
题目。
为了方便,下标从 \(1\) 开始。
如果要安装一个点,就要先安装从根节点到这个点路径上的所有点;如果要删除一个点,就要先删除以这个点为根的子树。
每个点只有两种状态:
-
已安装,这里用 \(1\) 表示。
-
未安装,这里用 \(0\) 表示。
两种操作(设操作前和为 \(a\),操作后和为 \(b\)):
-
安装 \(x\):将根到 \(x\) 的路径上的所有点变成 \(1\),操作总数:\(b-a\)。
-
卸载 \(x\):将以 \(x\) 为根的子树上的所有点变成 \(0\),操作总数:\(a-b\)。
转化到区间操作:
-
将某段全变成 \(t\)。
-
查询区间和。
懒标记 flag 有三种取值:
-
\(-1\),表示无操作。
-
\(1\),表示全变成 \(1\)。
-
\(0\),表示全变成 \(0\)。
参考代码:
#include<bits/stdc++.h>
#define mems(a,b) memset(a,b,sizeof a)
using namespace std;
const int N=100010;
int n,m;
int h[N],e[N],ne[N],idx;
int id[N],cnt;
int dep[N],sz[N],top[N],fa[N],son[N];
struct Tree{
int l,r;
int flag,sum;
}tr[N*4];
void add(int a,int b){
e[idx]=b;
ne[idx]=h[a];
h[a]=idx++;
}
void dfs1(int u,int depth){
dep[u]=depth;
sz[u]=1;
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
dfs1(j,depth+1);
sz[u]+=sz[j];
if(sz[son[u]]<sz[j]) son[u]=j;
}
}
void dfs2(int u,int t){
id[u]=++cnt;
top[u]=t;
if(!son[u]) return;
dfs2(son[u],t);
for(int i=h[u];~i;i=ne[i]){
int j=e[i];
if(j==son[u]) continue;
dfs2(j,j);
}
}
void pushup(int u){
tr[u].sum=tr[u<<1].sum+tr[u<<1|1].sum;
}
void pushdown(int u){
Tree &root=tr[u],&left=tr[u<<1],&right=tr[u<<1|1];
if(root.flag!=-1){
left.sum=root.flag*(left.r-left.l+1);
right.sum=root.flag*(right.r-right.l+1);
left.flag=right.flag=root.flag;
root.flag=-1;
}
}
void build(int u,int l,int r){
tr[u]={l,r,-1,0};
if(l==r) return;
int mid=l+r>>1;
build(u<<1,l,mid);
build(u<<1|1,mid+1,r);
}
void update(int u,int l,int r,int k){
if(l<=tr[u].l && r>=tr[u].r){
tr[u].flag=k;
tr[u].sum=k*(tr[u].r-tr[u].l+1);
return;
}
int mid=tr[u].l+tr[u].r>>1;
pushdown(u);
if(l<=mid) update(u<<1,l,r,k);
if(r>mid) update(u<<1|1,l,r,k);
pushup(u);
}
void update_path(int u,int v,int k){
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]) swap(u,v);
update(1,id[top[u]],id[u],k);
u=fa[top[u]];
}
if(dep[u]<dep[v]) swap(u,v);
update(1,id[v],id[u],k);
}
void update_tree(int u,int k){
update(1,id[u],id[u]+sz[u]-1,k);
}
int main(){
cin>>n;
mems(h,-1);
for(int i=2;i<=n;i++){
int p;
scanf("%d",&p);
p++;
add(p,i);
fa[i]=p;
}
dfs1(1,1);
dfs2(1,1);
build(1,1,n);
cin>>m;
char op[20];
int u;
while(m--){
scanf("%s%d",op,&u);
u++;
if(!strcmp(op,"install")){
int t=tr[1].sum;
update_path(1,u,1);
printf("%d\n",tr[1].sum-t);
}
else{
int t=tr[1].sum;
update_tree(u,0);
printf("%d\n",t-tr[1].sum);
}
}
return 0;
}

浙公网安备 33010602011771号