dfs序+欧拉序+重链剖分

dfs序

dfs序学习

构造: d f s dfs dfs 序是通过按照 d f s dfs dfs 时访问结点的先后顺序产生的序,一般维护两个数组 i n , o u t in,out in,out ,用 id 表示时间戳。每次新访问一个新结点 i n [ u ] = + + i d in[u]=++id in[u]=++id,访问结束后 o u t [ u ] = i d out[u]=id out[u]=id

用处: d f s dfs dfs 序可以把树上问题转化为区间问题,然后用一些线段树,树状数组之类的数据结构维护。

性质: v v v u u u 的子树,则 v 的时间戳一定在 u 的时间戳之间,即: i n [ u ] < i n [ v ] , o u t [ v ] < = o u t [ u ] in[u]<in[v],out[v]<=out[u] in[u]<in[v],out[v]<=out[u] ;并且 v 的序列是 u 的序列中的连续一段。更具体的:以 u 为根的子树所有结点是 d f s dfs dfs 序中连续的一段,在 [ i n [ u ] , o u t [ u ] ] [in[u],out[u]] [in[u],out[u]]

映射关系: s e q [ i n [ u ] ] = u seq[in[u]]=u seq[in[u]]=u

void dfs(int u,int fa){
	in[u]=++id;
	seq[id]=u;
	for(int i=vex[u];i;i=e[i].next){
		int v=e[i].v;
		if(v==fa)continue;
		dfs(v,u);
	}
	out[u]=id;
}

例题练习

例题1:树上单点修改,询问某点子树点权和

  • 问题分析: 线段树模板即可,单点修改 i n [ u ] in[u] in[u],区间查询 [ i n [ u ] , o u t [ u ] ] [in[u],out[u]] [in[u],out[u]]

例题2:树上路径修改,查询某个点的权值

  • 问题分析: 路径 ( x , y ) + v (x,y)+v (x,y)+v,相当于路径 ( r o o t , x ) + v , ( r o o t , y ) + v , ( r o o t , l c a ) − v , ( r o o t , f a ( l c a ) ) − v (root,x)+v,(root,y)+v,(root,lca)-v,(root,fa(lca))-v (root,x)+v,(root,y)+v,(root,lca)v,(root,fa(lca))v。那么问题就变成了:修改根到某个点的路径上所有点的值。假设需要修改路径 ( r o o t , x ) + v (root,x)+v (root,x)+v ,考虑这次修改只对子树中包含点 x 的点有贡献,不妨将问题转化为:单点修改 x 点的值,查询某点子树点权和。

例题3:树上路径修改,查询某点子树点权和

例题4:树上单点修改,查询路径点权和

例题5:树上某点子树所有点修改,查询路径点权和

例题6:树上某点子树所有点修改,查询某个点点权

例题7:树上某点子树所有点修改,查询某点子树点权和

欧拉序

欧拉序学习

定义: 欧拉序同样是通过按照 d f s dfs dfs 时访问结点的先后顺序产生的序,但是他对结点时间戳的记录与 d f s dfs dfs 序不同,同时它自身也有多种记录时间戳的方式,不同的记录方式能解决的问题不同。下面根据例题来学习几种欧拉序方式。

欧拉序构造1:每次访问一个结点时记录一次

构造方式:

Code:

void dfs(int u,int fa){
	E[++cnt]=u;
	in[u]=cnt;
	for(int i=vex[u];i;i=e[i].next){
		int v=e[i].v;
		if(v==fa)continue;
		dfs(v,u);
		E[++cnt]=u;
	}
	out[u]=cnt;
}

性质: 任意两个点的路径必然是欧拉序中的连续一段,并且是 [ i n [ x ] , i n [ y ] ] [in[x],in[y]] [in[x],in[y]]

例题1:欧拉序求 lca

  • 问题分析: 由于我们知道了任意两个点之间的路径一定是欧拉序中的连续一段 [ i n [ x ] , i n [ y ] ] [in[x],in[y]] [in[x],in[y]] ,那么这一段中,dep最小的点,就一定是 lca 了。相当于查询区间最小值,这一点刚好可以用 st 表实现。
void dfs(int u,int fa) {
	E[++cnt]=u;
	in[u]=cnt;
	dep[u]=dep[fa]+1;
	for(int i=vex[u]; i; i=e[i].next) {
		int v=e[i].v;
		if(v==fa)continue;
		dfs(v,u);
		E[++cnt]=u;
	}
	out[u]=cnt;
}
void init_ST() {
	for(int i=1; i<=cnt; i++)f[i][0]=E[i];//点编号 
	for(int p=1; (1<<p)<=cnt; p++) {
		for(int i=1; i+(1<<p)-1<=cnt; i++) {
			dep[f[i][p-1]]<dep[f[i+(1<<(p-1))][p-1]]?f[i][p]=f[i][p-1]:f[i][p]=f[i+(1<<(p-1))][p-1];
		}
	}
	int t=0;//log2打表
	for(int i=0; i<=cnt; i++) {
		while((1<<(t+1))<=i)t++;
		Log2[i]=t;
	}
}
int query(int u,int v){
	u=in[u],v=in[v];
	if(u>v)swap(u,v);
	int k=Log2[v-u+1];
	return dep[f[u][k]]<dep[f[v-(1<<k)+1][k]]?f[u][k]:f[v-(1<<k)+1][k]; 
}

例题2:欧拉序快速求两点最短路

  • 问题分析: 以任意一个点为根,构造出欧拉序,并预处理出 ST 表求 lca 。那么可以 O(1) 求 d i s ( x , y ) = d i s ( x ) + d i s ( y ) − 2 × d i s ( l c a ( x , y ) ) dis(x,y)=dis(x)+dis(y)-2\times dis(lca(x,y)) dis(x,y)=dis(x)+dis(y)2×dis(lca(x,y))

重链剖分

重链剖分学习

重链剖分的预处理

例题链接

  • 需要完成的功能: 路径修改,路径查询,子树修改,子树查询

  • 前言: 重链剖分也是一种通过 d f s dfs dfs 时访问结点的先后顺序产生的序。其通过先访问重儿子再访问轻儿子的方式,将大部分的树上路径修改,路径查询等问题,在 l o g 2 2 log_2^2 log22 内解决了。前面用 d f s dfs dfs 序和欧拉序的所有问题,重链剖分也都能解决。

  • 原理: 通过 d f s dfs dfs 序访问结点的先后顺序产生一个序,并将该序放入线段树中进行维护。

  • 预处理:

  • 第一次 dfs,统计size,求重儿子son,求深度dep,求父节点 f

  • 第二遍 dfs,根据先遍历重儿子,再遍历轻儿子的顺序,记录以下数据

  • i d [ i ] : id[i]: id[i]i 节点在线段树维护的数组中所对应的下标

  • s e g [ i ] : seg[i]: seg[i]线段树维护的数组中 i 数字所对应的节点

  • t o p [ i ] : top[i]: top[i]该条重链的头

  • 性质: 1.重链上的点编号是连续的;2.子树上的所有点编号也是连续的一段区间

int vex[N],k,son[N],f[N],dep[N],size[N],id[N],cnt,top[N],seg[N];
void dfs1(int u,int fa){//预处理出高度,父亲,子树大小,重儿子 
	f[u]=fa;
	dep[u]=dep[fa]+1;
	size[u]=1;
	for(int i=vex[u];i;i=e[i].next){
		int v=e[i].v;
		if(v==fa)continue;
		dfs1(v,u);
		size[u]+=size[v];
		if(size[v]>size[son[u]])son[u]=v;
	}
}
void dfs2(int u,int fa,int root){//预处理所在重链的头,线段树的映射关系 
	top[u]=root;
	seg[++cnt]=u;//seg[i]=u:线段树中第 i 个点是结点 u 
	id[u]=cnt;//id[u]: 线段树中结点 u 所在线段树位置 
	if(son[u]==0)return;
	dfs2(son[u],u,root);
	for(int i=vex[u];i;i=e[i].next){
		int v=e[i].v;
		if(v==fa||v==son[u])continue;
		dfs2(v,u,v);
	}
}

重链剖分的区间修改与区间查询

  • 问题分析: 对于查询路径 ( x , y ) (x,y) (x,y),假设 x 为较深的点
  • 情况1: 如果他们在一条重链上,由于重链上的编号是连续的,所以这条路径必然是线段树上 [ i n [ y ] , i n [ x ] ] [in[y],in[x]] [in[y],in[x]]
  • 情况2: 如果他们不在一条重链上,显然他们的路径是若干条重链 + 情况1
  • 方法: 反复让两个点中所在重链顶较深的点往上跳重链,维护重链顶到该点的路径,让点跳到重链顶的上一个结点,即下一条重链的某个点中。直到两个点在同一条重链中,最后再维护一下 [ i n [ y ] , i n [ x ] ] [in[y],in[x]] [in[y],in[x]]
int range(int x,int y){
    int ans=0;
    while(top[x]!=top[y]){//当两个点不在同一条链上 
        if(dep[top[x]]<dep[top[y]])swap(x,y);//令x为所在重链顶端的深度更深的那个点
        ans+=query(1,1,n,id[top[x]],id[x]);//维护x点到x所在链顶端
        //update(1,1,n,id[top[x]],id[x]);
        x=fa[top[x]];//令x跳到x所在重链顶端的上面一个点
    }
    //直到两个点处于一条链上
    if(dep[x]>dep[y])swap(x,y);//把x点深度更深的那个点
    ans+=query(1,1,n,id[x],id[y]);//维护最后一段
    //update(1,1,n,id[x],id[y]);
    return ans;
}

重链剖分的子树查询

方法: u u u 的子树在线段树 [ i d [ u ] , i d [ u ] + s i z e [ u ] − 1 ] [id[u],id[u]+size[u]-1] [id[u],id[u]+size[u]1]

int sontree(int u){
	query(1,1,n,id[u],id[u]+size[u]-1);
	//update(1,1,n,id[u],id[u]+size[u]-1);
} 

重链剖分总模板

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
struct E{
	int u,v,next;
}e[N*2];
int vex[N],k,son[N],f[N],dep[N],size[N],id[N],cnt,top[N],seg[N];
void dfs1(int u,int fa){//预处理出高度,父亲,子树大小,重儿子 
	f[u]=fa;
	dep[u]=dep[fa]+1;
	size[u]=1;
	for(int i=vex[u];i;i=e[i].next){
		int v=e[i].v;
		if(v==fa)continue;
		dfs1(v,u);
		size[u]+=size[v];
		if(size[v]>size[son[u]])son[u]=v;
	}
}
void dfs2(int u,int fa,int root){//预处理所在重链的头,线段树的映射关系 
	top[u]=root;
	seg[++cnt]=u;//seg[i]=u:线段树中第 i 个点是结点 u 
	id[u]=cnt;//id[u]: 线段树中结点 u 所在线段树位置 
	if(son[u]==0)return;
	dfs2(son[u],u,root);
	for(int i=vex[u];i;i=e[i].next){
		int v=e[i].v;
		if(v==fa||v==son[u])continue;
		dfs2(v,u,v);
	}
}
int range(int x,int y){
    int ans=0;
    while(top[x]!=top[y]){//当两个点不在同一条链上 
        if(dep[top[x]]<dep[top[y]])swap(x,y);//令x为所在重链顶端的深度更深的那个点
        ans+=query(1,1,n,id[top[x]],id[x]);//维护x点到x所在链顶端
        //update(1,1,n,id[top[x]],id[x]);
        x=fa[top[x]];//令x跳到x所在重链顶端的上面一个点
    }
    //直到两个点处于一条链上
    if(dep[x]>dep[y])swap(x,y);//把x点深度更深的那个点
    ans+=query(1,1,n,id[x],id[y]);//维护最后一段
    //update(1,1,n,id[x],id[y]);
    return ans;//y为lca 
}
int sontree(int u){
	query(1,1,n,id[u],id[u]+size[u]-1);
	//update(1,1,n,id[u],id[u]+size[u]-1);
} 
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值