dfs序和树链剖分两者都借助hash的思想是将树形转化成线性的算法,前者主要处理子树问题,后者主要处理链的问题。
1.dfs序
定义: 顾名思义是按照dfs的顺序标号。
重要性质:每棵子树内的标号都是连续的,某些链的标号也是连续的。
代码:
int timetmp;///时间戳,计数器
int in[maxn];///表示dfs进某节点的时间
int out[maxn];///表示dfs出某节点的时间
///差值就是子树的节点个数
vector<int>e[maxn];///存图
void dfs(int u,int fa){
in[u]=++timetmp;
for(int i=0;i<e[u].size();i++){
int j=e[u][i];
if(j==fa) continue;
dfs(j,u);
}
out[u]=timetmp;
}
例题1:(卿学姐给的例题 没找到来源)
题意:两种操作 1.将子树的所有节点的权值都+v 2.查询某子树的权值的max
思路:按照dfs序给所有节点标号,这样就将树上问题转化成了区间问题,由重要性质我们可以把操作一转化为区间加法,操作二转化为区间最值,用线段树维护就可以了。
例题2:(POJ3321)
题意:两种操作 1.修改某个节点的权值,如果原来是1,修改为0,如果原来是0,修改为1 ; 2.查询某子树的权值和
思路:转化为dfs序后,就转化成了单点修改、区间查询的问题,用树状数组或线段树维护即可。
2.树链剖分(重链剖分 logn)
基本概念:(sz[x]表示x的子树个数)
重儿子:某节点的所有子节点里sz[x]最大的子节点(如果有多个就随便取一个)
轻儿子:一个节点除了重儿子以外的儿子
重链:从一个轻儿子(根节点也是轻儿子)开始,每次往重儿子走连出的链
轻链:除了重链全是轻链
模拟过程:
第一遍dfs:记录节点的父亲、重儿子、深度、大小
第二遍dfs:记录节点权值的dfs序和时间戳,当前节点所在重链的头是谁
int son[maxn],id[maxn],fa[maxn],cnt,dep[maxn],siz[maxn],top[maxn];
///son[]重儿子编号,id[]新编号,fa[]父亲节点,cnt dfs_clock/dfs序,dep[]深度,siz[]子树大小,top[]当前链顶端节点
int res=0;
///查询答案
inline void dfs1(int x,int f,int deep){//x当前节点,f父亲,deep深度
dep[x]=deep;//标记每个点的深度
fa[x]=f;//标记每个点的父亲
siz[x]=1;//标记每个非叶子节点的子树大小
int maxson=-1;//记录重儿子的儿子数
for(int i=h[x];i;i=ne[i]){
int y=e[i];
if(y==f)continue;//若为父亲则continue
dfs1(y,x,deep+1);//dfs其儿子
siz[x]+=siz[y];//把它的儿子数加到它身上
if(siz[y]>maxson)son[x]=y,maxson=siz[y];//标记每个非叶子节点的重儿子编号
}
}
inline void dfs2(int x,int topf){//x当前节点,topf当前链的最顶端的节点
id[x]=++cnt;//标记每个点的新编号
wt[cnt]=w[x];//把每个点的初始值赋到新编号上来
top[x]=topf;//这个点所在链的顶端
if(!son[x])return;//如果没有儿子则返回
dfs2(son[x],topf);//按先处理重儿子,再处理轻儿子的顺序递归处理
for(int i=h[x];i;i=ne[i]){
int y=e[i];
if(y==fa[x]||y==son[x])continue;
dfs2(y,y);//对于每一个轻儿子都有一条从它自己开始的链
}
}
重要性质:
重链上的编号都是连续的。
任何一条路径都是由重链的一部分和叶子节点组成的。
除根节点外的任意一个节点的父节点一定在一条重链上。
然后一般就再用树状数组或线段树维护信息。
例题:(洛谷P3384)
思路:
树剖之后操作3和操作4都利用区间修改和区间查询可以解决。
inline int qSon(int x){///操作4
res=0;
query(1,1,n,id[x],id[x]+siz[x]-1);//子树区间右端点为id[x]+siz[x]-1
return res;
}
inline void updSon(int x,int k){//操作3
update(1,1,n,id[x],id[x]+siz[x]-1,k);
}
对于操作1和操作2,如果两个节点在一条重链上,问题就转化成了区间修改和区间查询;如果不在一条重链上,可以维护两个指针指向两个节点,不停地让dep[top[i]]大的节点的指针沿着所在重量往上跳,同时在线段树上进行操作,直到两指针到同一节点或同一重链。
int qRange(int x,int y){//操作2 表示求树从 x到 y 结点最短路径上所有节点的值之和
int ans=0;
while(top[x]!=top[y]){//当两个点不在同一条链上
if(dep[top[x]]<dep[top[y]])swap(x,y);//把x点改为所在链顶端的深度更深的那个点
res=0;
query(1,1,n,id[top[x]],id[x]);//ans加上x点到x所在链顶端 这一段区间的点权和
ans+=res;
ans%=mod;//按题意取模
x=fa[top[x]];//把x跳到x所在链顶端的那个点的上面一个点
}
//直到两个点处于一条链上
if(dep[x]>dep[y])swap(x,y);//把x点变为深度更深的那个点
res=0;
query(1,1,n,id[x],id[y]);//这时再加上此时两个点的区间和即可
ans+=res;
return ans%mod;
}
待补
【牛客】2020牛客暑期多校训练营(第七场)C- A National Pandemic