树链剖分总结(讲解)
前言
我们在进行树上操作时往往会遇见一下操作:
- 将 u \ u u节点到 v \ v v节点的最短路径上所有节点全部加上一个数 k \ k k。
- 求 u \ u u节点到 v \ v v几点的最短路径上所有点权之和。
- 求以 u \ u u为根的子树的点权之和。
若单独处理这些问题是容易的(差分,LCA,DFS预处理)。但这些操作混到一起就不能用这些方法解决了,所以我们就引入树链剖分。
树链剖分的相关约定
- 重儿子:父亲节点的所有儿子中子树结点数目最多的结点。
- 轻儿子:父亲节点中除了重儿子以外的儿子。
- 重边:父亲结点和重儿子连成的边。
- 轻边:父亲节点和轻儿子连成的边。
- 重链:由多条重边连接而成的路径。
- 轻链:由多条轻边连接而成的路径。
注意,若一个节点有多个重儿子则任选其一。
红色为重儿子,黄色为轻儿子,3号节点的重儿子也可以是6或4。
其中红色为重链。橘色为轻链或重链的顶端。
相关声明
声明 | 定义 |
---|---|
fa[u] | 结点 u \ u u的父亲节点。 |
deep[u] | 结点 u \ u u的深度值。 |
siz[u] | 以 u \ u u为根的子树大小。 |
son[u] | 节点 u \ u u的重儿子。 |
yuan[u] | 当前dfs标号在树中所对应的节点。 |
top[u] | 当前节点所在链的顶端节点。 |
id[u] | 树中每个节点剖分以后的新编号,即dfs序。 |
其中dfs的求法应先遍历重链,保证重链的dfs序是连续的。
像这样。
重链内容 | 重链dfs序 |
---|---|
2 , 3 , 5 | 2 , 3 , 4 |
9 , 10 | 8 , 9 |
树链剖分的主要思路
0.声明
#include<bits/stdc++.h>
using namespace std;
int n,m,hea[100100],len=0,ide=0,siz[100100],son[100100],top[100100],deep[100100],id[100100],fa[100100],yuan[100100];
struct nobe
{
int ne,to;
}e[200100];
inline int read()
{
int s=0,w=0;
char ch=0;
while(!isdigit(ch)){w|=ch=='-';ch=getchar();}
while(isdigit(ch)){s=(s<<3)+(s<<1)+(ch^0x30);ch=getchar();}
return w ? -s : s;
}
1.求出重儿子数组和子树大小(son[u],siz[u])
这个是容易理解的,dfs即可
inline void dfs1(int x,int f)
{
siz[x]=1;
fa[x]=f;
son[x]=0;
deep[x]=deep[f]+1;
int i=hea[x];
while(i)
{
int y=e[i].to;
if(y!=f)
{
dfs1(y,x);
if(siz[y]>siz[son[x]]) son[x]=y;
siz[x]+=siz[y];
}
i=e[i].ne;
}
}
2.求出链顶和dfs序(id[x],top[x],yuan[x])
注意,应优先dfs重儿子。
inline void dfs2(int x,int topp)
{
id[x]=++ide;
top[x]=topp;
yuan[ide]=x;
if(!son[x]) return ;
dfs2(son[x],topp);
int i=hea[x];
while(i)
{
int y=e[i].to;
if((y!=fa[x])&&(y!=son[x])) dfs2(y,y);
i=e[i].ne;
}
}
这是树链剖分的所有预处理。
我们的dfs序保证了每一条链上的点是连续的。
连续指id[x]==id[fa[x]]+1,即一个节点的dfs序比它父亲节点的dfs序大1。
那么我们就可以用数据结构来维护一条链的信息。
厉害的同学可能已经懂了。反正我不懂。
对于修改操作。
- 将 u \ u u节点到 v \ v v节点的最短路径上所有节点全部加上一个数。
我们先从
u
\ u
u节点和
v
\ v
v节点向上跳。
若两点不在一条链上时,每次从重链(轻链)上的一点跳到链顶(x=top[x]),再处理这一条链的贡献。
再一条链上就特判一下。
复杂度O(
l
o
g
2
n
\ log^{2}n
log2n)
inline void treeupdate()
{
int x,y,k;
x=read();
y=read();
k=read();//读入x,y,和修改的值。
while(top[x]!=top[y])//不在同一链上、
{
if(deep[top[x]]<deep[top[y]]) swap(x,y);
update(1,1,n,id[top[x]],id[x],k);
x=fa[top[x]];//跳出这条链。
}
if(deep[x]>deep[y]) swap(x,y);//在同一链上。
update(1,1,n,id[x],id[y],k);
}
路径上的查询
- 求 u \ u u节点到 v \ v v几点的最短路径上所有点权之和。
与修改类似,跳一跳即可。
复杂度O(
l
o
g
2
n
\ log^{2}n
log2n)
inline int treepathquery()
{
int x,y,k,ans=0;
x=read();
y=read();//读入x,y。
while(top[x]!=top[y])//不在同一链上、
{
if(deep[top[x]]<deep[top[y]]) swap(x,y);
ans+=query(1,1,n,id[top[x]],id[x]);
x=fa[top[x]];//跳出这条链。
}
if(deep[x]>deep[y]) swap(x,y);//在同一链上。
ans+=query(1,1,n,id[x],id[y]);
return ans;
}
子树查询
- 求以 u \ u u为根的子树的点权之和。
根据dfs序的定义,我们可以知道已知节点
u
\ u
u其子树大小为
s
i
z
e
\ size
size,那么它以及它的子树dfs序一定为
i
d
u
−
−
i
d
u
+
s
i
z
e
−
1
\ id_{u}--id_{u+size-1}
idu−−idu+size−1。
挺好理解的,不明白的自己画个小图模拟一下就是显然的了。
那就随便写写就有代码。
复杂度O(
l
o
g
n
\ logn
logn)
inline void treequery()
{
int x;
x=read();
printf("%lld\n",query(1,1,n,id[x],id[x]+siz[x]-1));//简单粗暴的查询。
}
具体大概就这样了。
总结
树链剖分可进行很多操作,远不止上面说的那么少。学好树链剖分,可瞎搞A题。
相信大家有举一反三的能力。
几道树链题。(树链剖分一堆码农题。)(这些都不是码农题。)
码农题。
前两道是板子,最后一道不算是。
最后祝大家用好树链剖分,熟练剖分。
魔法树AC代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,hea[100100],len=0,ide=0,siz[100100],son[100100],top[100100],deep[100100],id[100100],fa[100100];
struct nobe
{
int ne,to;
}e[200100];
struct tree
{
long long sum,lazy;
int siz;
}t[400100];
inline int read()
{
int s=0,w=0;
char ch=0;
while(!isdigit(ch)){w|=ch=='-';ch=getchar();}
while(isdigit(ch)){s=(s<<3)+(s<<1)+(ch^0x30);ch=getchar();}
return w ? -s : s;
}
//---------------------------------------------------------dfs---------------------------------------------------------
inline void adde(int a,int b)
{
e[++len].to=b;
e[len].ne=hea[a];
hea[a]=len;
}
inline void dfs1(int x,int f)
{
siz[x]=1;
fa[x]=f;
son[x]=0;
deep[x]=deep[f]+1;
int i=hea[x];
while(i)
{
int y=e[i].to;
if(y!=f)
{
dfs1(y,x);
if(siz[y]>siz[son[x]]) son[x]=y;
siz[x]+=siz[y];
}
i=e[i].ne;
}
}
inline void dfs2(int x,int topp)
{
id[x]=++ide;
top[x]=topp;
if(!son[x]) return ;
dfs2(son[x],topp);
int i=hea[x];
while(i)
{
int y=e[i].to;
if((y!=fa[x])&&(y!=son[x])) dfs2(y,y);
i=e[i].ne;
}
}
//---------------------------------------------------------dfs---------------------------------------------------------
//-------------------------------------------------------sigtree-------------------------------------------------------
inline void pushup(int k)
{
t[k].sum=t[k<<1].sum+t[k<<1|1].sum;
}
inline void pushdown(int k)
{
t[k<<1].sum+=t[k<<1].siz*t[k].lazy,
t[k<<1|1].sum+=t[k<<1|1].siz*t[k].lazy,
t[k<<1].lazy+=t[k].lazy,
t[k<<1|1].lazy+=t[k].lazy,
t[k].lazy=0;
}
inline void build(int k,int l,int r)
{
t[k].siz=r-l+1;
if(l==r)
{
t[k].sum=0;
return ;
}
int mid=(l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r),
pushup(k);
}
inline void update(int k,int l,int r,int ll,int rr,long long add)
{
if((l>=ll)&&(r<=rr))
{
t[k].sum+=t[k].siz*add;
t[k].lazy+=add;
return ;
}
if(t[k].lazy) pushdown(k);
int mid=(l+r)>>1;
if(rr<=mid) update(k<<1,l,mid,ll,rr,add);
else if(ll>mid) update(k<<1|1,mid+1,r,ll,rr,add);
else update(k<<1,l,mid,ll,rr,add),update(k<<1|1,mid+1,r,ll,rr,add);
pushup(k);
}
inline long long query(int k,int l,int r,int ll,int rr)
{
if(l>r) return 0;
if((l>=ll)&&(r<=rr)) return t[k].sum;
if(t[k].lazy) pushdown(k);
int mid=(l+r)>>1;
if(rr<=mid) return query(k<<1,l,mid,ll,rr);
else if(ll>mid) return query(k<<1|1,mid+1,r,ll,rr);
else return query(k<<1,l,mid,ll,rr)+query(k<<1|1,mid+1,r,ll,rr);
}
//-------------------------------------------------------sigtree-------------------------------------------------------
//--------------------------------------------------------treec--------------------------------------------------------
inline void treeupdate()
{
int x,y;
long long k;
x=read();
y=read();
++x;
++y;
k=read();
while(top[x]!=top[y])
{
if(deep[top[x]]<deep[top[y]]) swap(x,y);
update(1,1,n,id[top[x]],id[x],k);
x=fa[top[x]];
}
if(deep[x]>deep[y]) swap(x,y);
update(1,1,n,id[x],id[y],k);
}
inline void treequery()
{
int x;
x=read();
++x;
printf("%lld\n",query(1,1,n,id[x],id[x]+siz[x]-1));
}
//--------------------------------------------------------treec--------------------------------------------------------
signed main()
{
memset(t,0,sizeof(t));
memset(siz,0,sizeof(siz));
memset(hea,0,sizeof(hea));
n=read();
int i=1;
while(i<n)
{
int u,v;
u=read();
v=read();
++u;
++v;
adde(u,v);
adde(v,u);
++i;
}
fa[1]=0;
deep[0]=0;
dfs1(1,0);
dfs2(1,1);
build(1,1,n);
m=read();
while(m--)
{
char op=getchar();
while((op==' ')||(op=='\n')) op=getchar();
if(op=='A') treeupdate();
else treequery();
}
return 0;
}
软件包管理器AC代码
#include<bits/stdc++.h>
using namespace std;
int n,m,len=0,hea[100100];
int deep[100100],fa[100100],top[100100],son[100100],id[100100],siz[100100],yuan[100100],ide=0;
struct nobe
{
int ne,to;
}e[100100];
struct tree
{
int sum,siz,fl;
}t[400100];
inline int read()
{
int s=0,w=0;
char ch=0;
while(!isdigit(ch)){w|=ch=='-';ch=getchar();}
while(isdigit(ch)){s=(s<<3)+(s<<1)+(ch^0x30);ch=getchar();}
return w ? -s : s;
}
//----------------------------------------------------dfs----------------------------------------------------
inline void adde(int u,int v)
{
e[++len].to=v;
e[len].ne=hea[u];
hea[u]=len;
}
inline void dfs1(int x,int f)
{
fa[x]=f;
deep[x]=deep[f]+1;
son[x]=0;
siz[x]=1;
int i=hea[x];
while(i)
{
int y=e[i].to;
if(y!=f)
{
dfs1(y,x);
siz[x]+=siz[y];
if(siz[son[x]]<siz[y]) son[x]=y;
}
i=e[i].ne;
}
}
inline void dfs2(int x,int topp)
{
top[x]=topp;
++ide;
id[x]=ide;
yuan[ide]=x;
if(!son[x]) return ;
dfs2(son[x],topp);
int i=hea[x];
while(i)
{
int y=e[i].to;
if((y!=fa[x])&&(y!=son[x])) dfs2(y,y);
i=e[i].ne;
}
}
//----------------------------------------------------dfs----------------------------------------------------
//--------------------------------------------------sigtree--------------------------------------------------
inline void pushdata(int k)
{
t[k<<1].sum=t[k<<1].siz*t[k].fl;
t[k<<1|1].sum=t[k<<1|1].siz*t[k].fl;
t[k<<1].fl=t[k<<1|1].fl=t[k].fl;
t[k].fl=-1;
}
inline void build(int k,int l,int r)
{
t[k].siz=r-l+1;
t[k].sum=0;
t[k].fl=-1;
if(l==r) return ;
int mid=(l+r)>>1;
build(k<<1,l,mid);
build(k<<1|1,mid+1,r);
}
void update(int k,int l,int r,int ll,int rr,int val)
{
if((r<ll)||(l>rr)) return ;
if((r<=rr)&&(l>=ll))
{
t[k].sum=t[k].siz*val;
t[k].fl=val;
return ;
}
int mid=(l+r)>>1;
if(t[k].fl!=-1) pushdata(k);
update(k<<1,l,mid,ll,rr,val);
update(k<<1|1,mid+1,r,ll,rr,val);
t[k].sum=t[k<<1].sum+t[k<<1|1].sum;
}
inline int query(int k,int l,int r,int ll,int rr)
{
if((r<ll)||(l>rr)) return 0;
if((r<=rr)&&(l>=ll)) return t[k].sum;
if(t[k].fl!=-1) pushdata(k);
int mid=(l+r)>>1;
return query(k<<1,l,mid,ll,rr)+query(k<<1|1,mid+1,r,ll,rr);
}
//--------------------------------------------------sigtree--------------------------------------------------
void treechange(int u,int v,int val)
{
while(top[u]!=top[v])
{
if(deep[top[u]]<deep[top[v]]) swap(u,v);
update(1,1,n,id[top[u]],id[u],val);
u=fa[top[u]];
}
if(deep[u]>deep[v]) swap(u,v);
update(1,1,n,id[u],id[v],val);
}
int main()
{
memset(t,0,sizeof(t));
memset(hea,0,sizeof(hea));
n=read();
int i=2;
while(i<=n)
{
int a;
a=read();
++a;
adde(a,i);
++i;
}
fa[1]=0;
deep[0]=0;
dfs1(1,0);
dfs2(1,1);
build(1,1,n);
m=read();
char s[10];
while(m--)
{
scanf("%s",s);
int x;
x=read();
++x;
int last=t[1].sum;
if(s[0]=='i')
{
treechange(1,x,1);
int now=t[1].sum;
printf("%d\n",abs(now-last));
}
if(s[0]=='u')
{
update(1,1,n,id[x],id[x]+siz[x]-1,0);
int now=t[1].sum;
printf("%d\n",abs(last-now));
}
}
return 0;
}
RPRPRP
/*
1996年:东方灵异传(TOH1)
1997年:东方封魔录(TOH2)
1997年:东方梦时空(TOH3)
1998年:东方幻想乡(TOH4)
1998年:东方怪绮谈(TOH5)
2002年:东方红魔乡(TOH6)
2003年:东方妖妖梦(TOH7)
2004年:东方萃梦想(TOH7.5)
2004年:东方永夜抄(TOH8)
2005年:东方花映冢(TOH9)
2005年:东方文花帖(TOH9.5)
2007年:东方风神录(TOH10)
2008年:东方绯想天(TOH10.5)
2008年:东方地灵殿(TOH11)
2009年:东方星莲船(TOH12)
2009年:东方非想天则(TOH12.3)
2010年:东方文花帖DS(TOH12.5)
2010年:东方三月精(TOH12.8)
2011年:东方神灵庙(TOH13)
2013年:东方心绮楼(TOH13.5)
2013年:东方辉针城(TOH14)
2014年:弹幕天邪鬼(TOH14.3)
2014年:东方深秘录(TOH14.5)
2015年:东方绀珠传(TOH15)
2017年:东方凭依华(TOH15.5)
2017年:东方天空璋(TOH16)
*/
//RP++