树的序列化——浅谈 dfn 与欧拉序列

本文介绍了树的序列化方法,包括dfn序列和两种类型的欧拉序列。dfn序列特点是祖先总在子孙前,不能直接复原树结构,但在树上单点修改和子树求和问题中有应用。通过树状数组可以优化查询复杂度。欧拉序列分为两类,第一类包含进出并使叶节点连续,第二类则不重复记录叶子节点,它们都可以复原树结构,其中第二类欧拉序列可用于推导节点关系。

dfndfndfn 序列

  1. 定义:dfn[u] 表示 u 在 dfs 时第几个访问到。例如 dfs 序列为 1423, 则 dfn 序列为 1342。
  2. 特点:
    • 祖先总在子孙前
    • 子树总是连续段:以 u 为根的子树为区间 [dfnu,dfnu+szu−1][dfn_u,dfn_u+sz_u-1][dfnu,dfnu+szu1]
    • 不能直接复原出树的结构,但借助 szusz_uszu 可以复原。
struct subTree{int L,R;}T[maxn];//子树区间
int dfn[maxn],timer;//dfn 序列与时间戳
vector<int> e[maxn];//模拟邻接表
inline void dfs(int u,int fa){
	T[u].L=dfn[u]=++timer;
	for(int i=0;i<e[u].size();i++){
		int v=e[u][i];
		if(v!=fa) dfs(v,u);
	}
	T[u].R=timer;//dfn[u]+sz[u]-1, 因此不用 ++timer 
}
  1. 应用:判断 uuu 是否为 vvv 的严格直系祖先:v∈uv\in uvu && v≠uv\ne uv=u,即 [Tu.L,Tu.R][T_u.L,T_u.R][Tu.L,Tu.R] 包含 [Tv.L,Tv.R][T_v.L,T_v.R][Tv.L,Tv.R]
inline bool up(int u,int v){
	return T[u].L<T[v].L&&T[v].R<=T[u].R;
//	return T[u].L<=T[v].L&&T[v].R<=T[u].R; //非严格直系祖先 
}
  1. 例题:树上单点修改,子树求和。

首先考虑暴力:一遍 dfs 求出祖孙关系,用 pup_upu 表示 uuu 的父亲节点。对于单点修改操作,直接暴力找到结点然后修改,子树查询时利用 dfs 累计子树和 ∑v∈uvalue(v)\sum\limits_{v\in u} value(v)vuvalue(v)。复杂度为 Θ(qlog⁡n)\Theta(q\log n)Θ(qlogn),但最坏为 O(qn)O(qn)O(qn),当树退化成链时。

对于正解,考虑将树利用 dfn 序列化,这样单点修改对应 dfnudfn_udfnu 的修改,而子树查询转换成区间求和。因此用树状数组维护即可。复杂度 O(qlog⁡n)O(q\log n)O(qlogn)

#include<bits/stdc++.h>
using namespace std;
#define rep(ii,aa,bb) for(re int ii = aa; ii <= bb; ii++)
#define Rep(ii,aa,bb) for(re int ii = aa; ii >= bb; ii--)
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 4e5+5;
namespace IO_ReadWrite {
	#define re register
	#define gg (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1<<21, stdin), p1 == p2) ? EOF :*p1++)
	char buf[1<<21], *p1 = buf, *p2 = buf;
	template <typename T>
	inline void read(T &x){
		x = 0; re T f=1; re char c = gg;
		while(c > 57 || c < 48){if(c == '-') f = -1;c = gg;}
		while(c >= 48 &&c <= 57){x = (x<<1) + (x<<3) + (c^48);c = gg;}
		x *= f;return;
	}
	inline void ReadChar(char &c){
		c = gg;
		while(!isalpha(c)) c = gg;
	}
	template <typename T>
	inline void write(T x){
		if(x < 0) putchar('-'), x = -x;
		if(x > 9) write(x/10);
		putchar('0' + x % 10);
	}
	template <typename T>
	inline void writeln(T x){write(x); putchar('\n');}
}
using namespace IO_ReadWrite;
int n,q,timer,dfn[maxn],c[maxn];
vector<int> e[maxn];
struct subTree{int L,R;}T[maxn];
inline void dfs(int u,int fa){
	T[u].L=dfn[u]=++timer;
	for(int i=0;i<e[u].size();i++){
		int v=e[u][i];
		if(v==fa) continue;
		dfs(v,u);
	}
	T[u].R=timer;
}//dfs 求 dfn 序列以及子树区间
inline int lowbit(int x){return x&(-x);}
inline void update(int x,int v){
	for(;x<=n+1;x+=lowbit(x)) 
		c[x]+=v;
}
inline int Sum(int x){
	int res=0;
	for(;x;x-=lowbit(x)) res+=c[x];
	return res;
}//树状数组
int main(){
	scanf("%d",&n);
	rep(i,1,n-1){
		int u,v;
		scanf("%d%d",&u,&v);
		e[u].push_back(v);
		e[v].push_back(u);
	}
	dfs(1,0);
	scanf("%d",&q);
	while(q--){
		char op[10]={0};
		scanf("%s",op);
		if(op[0]=='L'){
			int u,v;
			scanf("%d%d",&u,&v);
			update(dfn[u]+1,-v);//单点修改
		}
		else{
			int u;
			scanf("%d",&u);
			printf("%d ",(Sum(T[u].R+1)-Sum(T[u].L)));//区间求和
		}
	}
	return 0;
}

欧拉序列

第一类欧拉序列:dfs 序列,但包含进出(叶节点重复)
性质:

  • 长度为 2n
  • 叶节点连续出现

第二类欧拉序列:每次访问(包括回溯时)即记录,不重复记录叶子节点

  • 长度为 2n-1
  • 根节点作为隔板分割子树
  • 能够复原出原树的结构

第二类欧拉序列推导 pu,depu,szup_u,dep_u,sz_upu,depu,szu:

  • 若一个数在 euler 中第一次出现,则 pu=euleri−1p_u=euler_{i-1}pu=euleri1
    if(i==T[euler[i]].L) p[euler[i]]=euler[i-1];
  • dep[u]=dep[p[u]]+1
  • [T[u].L,T[u].R] 出现的数字种类为 szusz_uszu
    将第一次出现标记为 1, 否则标记为 0, 转换为区间和
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值