C++ 知识点选讲 8——DFS 序(真的可以将图映射为序列吗?)
学了这个就跳着讲吧。
个人认为是橙,但是它经常和一些别的东西混用。比如下面这道题:
有 \(q\) 组询问,每次询问有两种操作:
\(1 \ u \ x\):把以节点 \(u\) 为根的子树中所有点的权值都加上 \(x\)
\(2 \ u\):查询节点 \(u\) 的权值
如果 \(q \le 10^5\) 或者更大,暴力法就不行了。DFS 序可以将其映射为数列,再配合树状数组或线段树解决这个问题。
先来讲讲如何实现。
维护一个时间戳 \(id\),声明两个数组 \(in\) 和 \(out\)。当 DFS 搜索到一个节点时(设这个节点为 \(now\)),将 \(in_{now}\) 赋值为 \(id\),随后遍历每一个子节点。临回溯时将 \(out_{now}\) 赋值为 \(id\)。
模板:
void dfs(int x,int v){
fa[x]=v;
dfn[x]=++id;
siz[x]=1;
dep[x]=dep[v]+1;
for(int i=0; i<G[x].size(); i++){
int tx=G[x][i];
if(tx!=v){
dfs(tx,x);
siz[x]+=siz[tx];
}
}
out[x]=id;
}
(如上程序所示,一次 dfs 可以统计许多东西)
打表输出每个节点的 \(in\) 和 \(out\),能发现一个性质——节点 \(now\) 的子树中所有节点的 \(in\) 值,恰好构成一个连续区间 \([in_u,out_u]\),为我们映射提供有利条件。
再配上树状数组或线段树,上面的问题可以很巧妙地解决。因为操作 \(1\) 就是树状数组的区间加,操作 \(2\) 就是树状数组单点查询。
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
vector<int> G[50005];
int P[50005];
int id,a[50005],n,in[50005],out[50005];
void dfs(int x,int v){
// fa[x]=v;
in[x]=++id;
for(int i=0; i<G[x].size(); i++){
int tx=G[x][i];
if(tx!=v){
dfs(tx,x);
}
}
out[x]=id;
}
int T[50005];
int lb(int x){
return x&(-x);
}
void Add(int x,int k){
while(x<=n){
T[x]+=k;
x+=lb(x);
}
}
int Q(int x){
int sum=0;
while(x>=1){
sum+=T[x];
x-=lb(x);
}
return sum;
}
signed main(){
int q;
scanf("%lld %lld",&n,&q);
for(int i=1; i<=n; i++){
scanf("%lld",&a[i]);
// Add(in[i],a[in[i]]);
}
for(int i=1; i<n; i++){
int u,v;
scanf("%lld %lld",&u,&v);
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1,0);
for(int i=1; i<=n; i++){
P[in[i]]=a[i];
}
for(int i=1; i<=n; i++) Add(i,P[i]-P[i-1]);
while(q--){
int op;
scanf("%lld",&op);
if(op==1){
int l,r;
scanf("%lld %lld",&l,&r);
Add(in[l],r),Add(out[l]+1,-r);
}else{
int l;
scanf("%lld",&l);
printf("%lld\n",Q(in[l]));
}
}
return 0;
}
这是 DFS 序最简单的应用。

浙公网安备 33010602011771号