dfndfndfn 序列
- 定义:dfn[u] 表示 u 在 dfs 时第几个访问到。例如 dfs 序列为 1423, 则 dfn 序列为 1342。
- 特点:
- 祖先总在子孙前
- 子树总是连续段:以 u 为根的子树为区间 [dfnu,dfnu+szu−1][dfn_u,dfn_u+sz_u-1][dfnu,dfnu+szu−1]。
- 不能直接复原出树的结构,但借助 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
}
- 应用:判断 uuu 是否为 vvv 的严格直系祖先:v∈uv\in uv∈u && 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; //非严格直系祖先
}
- 例题:树上单点修改,子树求和。
首先考虑暴力:一遍 dfs 求出祖孙关系,用 pup_upu 表示 uuu 的父亲节点。对于单点修改操作,直接暴力找到结点然后修改,子树查询时利用 dfs 累计子树和 ∑v∈uvalue(v)\sum\limits_{v\in u} value(v)v∈u∑value(v)。复杂度为 Θ(qlogn)\Theta(q\log n)Θ(qlogn),但最坏为 O(qn)O(qn)O(qn),当树退化成链时。
对于正解,考虑将树利用 dfn 序列化,这样单点修改对应 dfnudfn_udfnu 的修改,而子树查询转换成区间求和。因此用树状数组维护即可。复杂度 O(qlogn)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=euleri−1
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, 转换为区间和
本文介绍了树的序列化方法,包括dfn序列和两种类型的欧拉序列。dfn序列特点是祖先总在子孙前,不能直接复原树结构,但在树上单点修改和子树求和问题中有应用。通过树状数组可以优化查询复杂度。欧拉序列分为两类,第一类包含进出并使叶节点连续,第二类则不重复记录叶子节点,它们都可以复原树结构,其中第二类欧拉序列可用于推导节点关系。

683

被折叠的 条评论
为什么被折叠?



