01_树的 dfs 序

大家好,从今天开始,我将在这一个专辑当中陆续给大家分享算法竞赛当中的进阶算法,大家如果对算法竞赛有兴趣的化,可以关注我一下,相信一定会给大家带来收获。

这一期,我讲给大家分享的是++树的 dfs 序++ ,一个比较简单的算法知识,但是简单归简单,它是后续较难算法的奠基石,后面很多较难的算法知识都会用到树的 dfs 序,所以是很重要的。

那么废话不多说,我们直接开始:

一:树的 dfs 序

树的 dfs 序,顾名思义,就是对一棵树进行 dfs 所得到的序列

树的创建方式是不唯一的,树的遍历方式也是不唯一的,那么树的 dfs 序就会有很多种形式

今天,我先来给大家介绍一种基于 "先序" 遍历形成的 dfs 序,也称作 dfn 序或者时间戳

这种 dfs 序是指在深度优先遍历的过程中,按照访问结点的顺序给结点进行编号,如下图所示:

至于先遍历哪一个孩子结点,其实是无所谓的,不影响后面的性质。

二:代码实现

我们要编写代码求出一棵树的 dfn 序:

1. idx 表示新遍历到一个结点,它在 dfn 序中的编号。

2. dfn[x] 表示:x 结点的 dfn 序。(一次 dfs 就可以维护出来)

3. sz[x] 表示:以 x 为根的子树的大小(树形 dp 就可以维护出来)(后面有用)

cpp 复制代码
vector<int> edges[N];

int dfn[N], sz[N], idx; // dfs 序,子树的大小

void dfs(int x, int fa)
{
	dfn[x] = ++idx;
	sz[x] = 1;
	for(auto y : edges[x])
	{
		if(y == fa) continue;
		dfs(y, x)
		sz[x] += sz[y];
	}
} 

三:性质

性质 1:

在一棵子树中,dfn 序是连续的。(通过 dfs 的过程就可以证明其正确性)

性质 2:

祖先节点的 dfn 序小于子孙节点的 dfn 序。(通过 dfs 的过程就可以证明其正确性)

性质 3:

以 x 为根的子树中,dfn 序为 [dfn[x], dfn[x] + sz[x] - 1](通过 dfs 的过程就可以证明其正确性)

上面所列的三个性质只需要记住结论就可以了,如果实在不好理解的话,就画图看一看:

四:应用

应用 1:

如何判断结点 y 是否在以结点 x 为根的子树中???

在我们没有学习树的 dfn 序之前,我们的做法是:

在 dfs 的过程中,维护出每一个结点的父亲结点信息 fa 数组。然后以 y 结点为起点,一直向上找 x,如果能找到,则说明结点 y 在以结点 x 为根的子树中,否则,结点 y 不在以结点 x 为根的子树中。

这样的话,单次判断的时间复杂度就会达到 O(n)。

如果是q** 次判断,时间复杂度就会达到 O(n * q)。**

我们学习完树的 dfn 序之后,就有更高效的解法了:

我们可以通过一次 dfs 预处理出 dfn 数组。然后每一次判断,就去看一下 dfn[y] 是否在

[dfn[x], dfn[x] + sz[x] - 1] 中就可以了。

这样的话,预处理的时间复杂度为 O(n),q 次询问的时间复杂度为 O(q)。

整体时间复杂度就为 O(n + q)(线性级别)了。

应用 2:

在一些树上问题中,利用树的 dfs 序,可以将 "树上问题" 转变成 "序列问题"。

比如说:

将以 x 为根的子树,所有的结点权值全部加 1,对应的就是将序列**[dfn[x], dfn[x] + sz[x] - 1] 中的元素全部增加 1。**

树上问题转化成为序列问题,我们就可以用之前学过的各种数据结构(线段树、树状数组......)来维护这些信息了。

五:模板题

接下来就是两道关于树的 dfs 序的**模板题目,**大家既然已经看到了这里,还是希望大家自己先尝试一下,看看能不能解决,再去对照我的代码。

题目链接题目一:DFS 序 1 题目二:DFS 序 2

题目描述 + 算法原理:

代码实现:

题目一:DFS 序 1:

cpp 复制代码
#include <iostream>
#include <vector>

using namespace std;

#define lowbit(x) (x & -x)
typedef long long LL;
const int N = 1e6 + 10;

int n, m, r, a[N];
vector<int> edges[N];
int dfn[N], sz[N], idx;
LL s[N]; // 树状数组

void modify(int x, LL k)
{
	for(int i = x; i <= n; i += lowbit(i)) s[i] += k;
}

LL query(int x)
{
	LL sum = 0;
	for(int i = x; i; i -= lowbit(i)) sum += s[i];
	return sum;
}

void dfs(int x, int fa)
{
	dfn[x] = ++idx;
	sz[x] = 1;
	
	modify(dfn[x], a[x]); // 初始的权值
	for(auto y : edges[x])
	{
		if(y == fa) continue;
		dfs(y, x);
		sz[x] += sz[y];
	} 
}

int main()
{
	cin >> n >> m >> r;
	for(int i = 1; i <= n; i++) cin >> a[i];
	for(int i = 1; i < n; i++)
	{
		int x, y; cin >> x >> y;
		edges[x].push_back(y);
		edges[y].push_back(x);
	}
	
	dfs(r, 0);
	
	while(m--)
	{
		int op, a, x; cin >> op >> a;
		if(op == 1)
		{
			cin >> x;
			modify(dfn[a], x);
		}
		else
		{
			int l = dfn[a], r = dfn[a] + sz[a] - 1;
			cout << query(r) - query(l - 1) << endl;
		}
	}
	
	return 0;
} 

题目二:DFS 序 2:

cpp 复制代码
#include <iostream>
#include <vector>

using namespace std;

#define lc p << 1
#define rc p << 1 | 1
typedef long long LL;
const int N = 1e6 + 10;

int n, m, r, a[N];
vector<int> edges[N];
int dfn[N], sz[N], rnk[N], idx;

struct node
{
	int l, r;
	LL sum, add;
}tr[N << 2];

void dfs(int x, int fa)
{
	dfn[x] = ++idx;
	rnk[idx] = x;
	sz[x] = 1;
	for(auto y : edges[x])
	{
		if(y == fa) continue;
		dfs(y, x);
		sz[x] += sz[y];
	}
}

void pushup(int p)
{
	tr[p].sum = tr[lc].sum + tr[rc].sum;
}

void build(int p, int l, int r)
{
	tr[p] = {l, r, a[rnk[l]], 0};
	if(l == r) return;
	
	int mid = (l + r) / 2;
	build(lc, l, mid); build(rc, mid + 1, r);
	pushup(p);
}

void lazy(int p, LL k)
{
	tr[p].sum += (tr[p].r - tr[p].l + 1) * k;
	tr[p].add += k;
}

void pushdown(int p)
{
	if(tr[p].add)
	{
		lazy(lc, tr[p].add);
		lazy(rc, tr[p].add);
		tr[p].add = 0;
	}
}

void modify(int p, int x, int y, LL k)
{
	int l = tr[p].l, r = tr[p].r;
	if(x <= l && r <= y)
	{
		lazy(p, k);
		return;
	}
	
	pushdown(p);
	int mid = (l + r) >> 1;
	if(x <= mid) modify(lc, x, y, k);
	if(y > mid) modify(rc, x, y, k);
	pushup(p);
}

LL query(int p, int x, int y)
{
	int l = tr[p].l, r = tr[p].r;
	if(x <= l && r <= y) return tr[p].sum;
	
	pushdown(p);
	int mid = (l + r) >> 1;
	LL s = 0;
	if(x <= mid) s += query(lc, x, y);
	if(y > mid) s += query(rc, x, y);
	
	return s;
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0); cout.tie(0);
	
	cin >> n >> m >> r;
	for(int i = 1; i <= n; i++) cin >> a[i];
	for(int i = 1; i < n; i++)
	{
		int a, b; cin >> a >> b;
		edges[a].push_back(b);
		edges[b].push_back(a);
	}
	
	dfs(r, 0);
	build(1, 1, n);
	
	while(m--)
	{
		int op, a, x; cin >> op >> a;
		int l = dfn[a], r = dfn[a] + sz[a] - 1;
		if(op == 1)
		{
			cin >> x;
			modify(1, l, r, x);
		}
		else
		{
			cout << query(1, l, r) << endl;
		}
	}
	
	return 0;
}

好的,今天的分享就到这里了,看到最后的你是不是收获慢慢呢~~~,

如果是的话,别忘了一键三连哦~~~

如果还是有问题的话,可以下来和我交流,或者直接在评论区指明。

谢谢大家的观看,我们下一期再见。

相关推荐
isyoungboy2 小时前
洪水法实现Region RLE的fill_up算法
算法
2401_841495642 小时前
自然语言处理实战——基于BP神经网络的命名实体识别
人工智能·python·神经网络·算法·机器学习·自然语言处理·命名实体识别
wjykp2 小时前
88~93感知机f
人工智能·算法
良木生香2 小时前
【数据结构-初阶】二叉树---链式存储
c语言·数据结构·c++·算法·蓝桥杯·深度优先
Binky6782 小时前
力扣--贪心(2)+动规(1)
算法·leetcode·职场和发展
雾喔2 小时前
1970. 你能穿过矩阵的最后一天 + 今年总结
线性代数·算法·矩阵
长安er11 小时前
LeetCode215/347/295 堆相关理论与题目
java·数据结构·算法·leetcode·
元亓亓亓11 小时前
LeetCode热题100--62. 不同路径--中等
算法·leetcode·职场和发展
小白菜又菜11 小时前
Leetcode 1925. Count Square Sum Triples
算法·leetcode