DFS 树

声明 本文部分内容来自 Codeforces 上的一篇博客,侵删。

DFS 是一种常见的图遍历方法。

考虑 无向图 的遍历过程:我们访问一个节点,遍历它的所有相邻节点,如果没有访问则去访问。不难发现每个节点只会被访问一次,也即这些节点和所有访问到的边可以构成一棵树,我们称这棵树为 DFS 树。访问过的边称为生成边(span edge),没有访问的称为后向边(back edge)。

仔细观察后向边,我们可以发现一些性质:

  • 每条后向边只会连接祖先和子孙,不会有兄弟相连。

这个很好证明,如果有一条边连接了兄弟,那么在 DFS 的过程中一定会先访问到其中一个兄弟,然后通过这条边访问另一个兄弟,而不是退回祖先处再去访问。也即这条边一定是生成边。

  • 每条后向边对应一个环,每个环也对应一条后向边。

证明也是很显然的,通过观察 DFS 树的样子即可完成。

有了这些性质 DFS 树可以干什么呢?最经典的应用就是 Tarjan 的使用 low 数组寻找割点的算法了。

我们考虑什么情况下 非根 节点 \(u\) 是割点:

  • 当且仅当存在一个儿子,使得儿子节点的子树中不存在任何一条后向边连接 \(u\) 的祖先,或者说没有一条后向边跨过(passes over)\(u\)。

证明是显然的,不再赘述。

具体实现中,我们令 \(dfn[u]\) 表示 \(u\) 是第 \(dfn[u]\) 个被访问到的,\(low[u]\) 为 DFS 树上 \(u\) 的儿子(包含 \(u\) 自己)可以访问到的所有节点中 \(dfn\) 最小的。那么对于节点 \(u\),进行以下操作:

procedure DFS(u)
    ind <- ind + 1
    dfn[u] <- low[u] <- ind

    childnum <- 0
    for v 可以被 u 访问
        if 访问过 v then low[u] <- min(low[u], dfn[v])
        else
            DFS(v)
            low[u] <- min(low[u], low[v])
            if low[v] = dfn[u] then childnum <- childnum + 1
            end if
        end if
    end for

    if childnum >= 1
        u 为割点
    end if
end procedure

注意 我们不只访问儿子节点,所以不需要特判父亲

而对于根节点,我们需要其有两个以上儿子。在具体实现中可以令 \(childnum = -1\)

给出 C++ 实现

洛谷板子题

#include<bits/stdc++.h>
using namespace std;

const int maxn = 2E+4 + 5;
const int maxm = 1E+5 + 5;

int n, m, root;
int tot, first[maxn];
int ind, cnt, p[maxn];
int dfn[maxn], low[maxn];
struct Edge {
	int to, next;
} e[maxm * 2];

inline void Add(int x, int y)
{
	e[++tot] = { y, first[x] };
	first[x] = tot;
}

void DFS(int u)
{
	dfn[u] = low[u] = ++ind;

	int childnum = 0;
	if(u == root) childnum = -1;

	for(int i = first[u]; i; i = e[i].next) {
		int v = e[i].to;

		if(!dfn[v]) {
			DFS(v), low[u] = min(low[u], low[v]);
			if(low[v] == dfn[u]) ++childnum;
		}
		else low[u] = min(low[u], dfn[v]);
	}

	if(childnum >= 1) p[++cnt] = u;
}

int main()
{
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= m; ++i) {
		int x, y;

		scanf("%d%d", &x, &y);
		Add(x, y), Add(y, x);
	}

	for(int i = 1; i <= n; ++i)
		if(!dfn[i]) root = i, DFS(i);

	sort(p + 1, p + cnt + 1);

	printf("%d\n", cnt);
	for(int i = 1; i <= cnt; ++i)
		printf("%d\n", p[i]);
}

而事实上,DFS 树还可以处理如无向图改成有向图,二分图划分之类的东西,特别是处理仙人掌时更是得心应手。

原文地址:https://www.cnblogs.com/whx1003/p/12635387.html

时间: 2024-11-05 13:34:38

DFS 树的相关文章

BZOJ 4541: [Hnoi2016]矿区 平面图转对偶图+DFS树

4541: [Hnoi2016]矿区 Time Limit: 30 Sec  Memory Limit: 512 MBSubmit: 433  Solved: 182[Submit][Status][Discuss] Description 平面上的矿区划分成了若干个开发区域.简单地说,你可以将矿区看成一张连通的平面图,平面图划分为了若 干平面块,每个平面块即为一个开发区域,平面块之间的边界必定由若干整点(坐标值为整数的点)和连接这些整点 的线段组成.每个开发区域的矿量与该开发区域的面积有关:具

【BZOJ4238】电压 DFS树

[BZOJ4238]电压 Description 你知道Just Odd Inventions社吗?这个公司的业务是“只不过是奇妙的发明(Just Odd Inventions)”.这里简称为JOI社. JOI社的某个实验室中有着复杂的电路.电路由n个节点和m根细长的电阻组成.节点被标号为1~N 每个节点有一个可设定的状态[高电压]或者[低电压].每个电阻连接两个节点,只有一端是高电压,另一端是低电压的电阻才会有电流流过.两端都是高电压或者低电压的电阻不会有电流流过. 某天,JOI社为了维护电路

【bzoj4016】[FJOI2014]最短路径树问题 堆优化Dijkstra+DFS树+树的点分治

题目描述 给一个包含n个点,m条边的无向连通图.从顶点1出发,往其余所有点分别走一次并返回. 往某一个点走时,选择总长度最短的路径走.若有多条长度最短的路径,则选择经过的顶点序列字典序最小的那条路径(如路径A为1,32,11,路径B为1,3,2,11,路径B字典序较小.注意是序列的字典序的最小,而非路径中节点编号相连的字符串字典序最小).到达该点后按原路返回,然后往其他点走,直到所有点都走过. 可以知道,经过的边会构成一棵最短路径树.请问,在这棵最短路径树上,最长的包含K个点的简单路径长度为多长

zstu.4191: 无向图找环(dfs树 + 邻接表)

4191: 无向图找环 Time Limit: 5 Sec  Memory Limit: 128 MB Submit: 117  Solved: 34 Description 给你一副无向图,每条边有边权,保证图联通,现在让你判断这个图是否有异或值大于零的环存在. Input 多组测试数据,每组先输入两个数n m,表示图的点跟边的数量. 然后是m行,每行三个数a b c.代表一条边的起点,终点,边权. 1 <= n<= 100000, 1 <= m <= 200000. 1 <

【BZOJ4424】Cf19E Fairy DFS树

[BZOJ4424]Cf19E Fairy Description 给定 n 个点,m 条边的无向图,可以从图中删除一条边,问删除哪些边可以使图变成一个二分图. Input 第 1 行包含两个整数 n,m.分别表示点数和边数.第 2 到 m+1 行每行两个数 x,y 表示有一条(x,y)的边. Output 输出第一行一个整数,表示能删除的边的个数.接下来一行按照从小到大的顺序输出边的序号. Sample Input 4 4 1 2 1 3 2 4 3 4 Sample Output 4 1 2

HDOJ 4582 - DFS spanning tree - DFS树,贪心

题目大意: 给定一个N个点.M条边的无向图Graph,以及从点1开始进行DFS形成的树Tree,定义"T-Simple Circle"为Graph中的环,要求其中只含一条不属于Tree的边. 将Graph中的一些边进行染色,使得其中每个T-simple Circle都至少包含一条被染色的边,求最少需要染色的边数. N≤2e3,M≤2e4 本题关键的一点在于Tree是一棵DFS生成树,这样Tree以外的边只可能将某个点与它在Tree中的祖先相连(用反证法可以证明,只有这样才能维持DFS树

BZOJ5203 [NEERC2017 Northern] Grand Test 【dfs树】【构造】

题目分析: 首先观察可知这是一个无向图,那么我们构建出它的dfs树.由于无向图的性质我们可以知道它的dfs树只有返祖边.考虑下面这样一个结论. 结论:若一个点的子树中(包含自己)有两个点有到它祖先的返祖边(不包括到它自己), 首先我们证明S和T肯定在DFS树中是祖先关系,接着证明到T至少有一条返祖边,那么这个结论就是显然的了. 代码: 1 #include<bits/stdc++.h> 2 using namespace std; 3 4 const int maxn = 102000; 5

HDU 5927 Auxiliary Set 【DFS+树】(2016CCPC东北地区大学生程序设计竞赛)

Auxiliary Set Time Limit: 9000/4500 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 873    Accepted Submission(s): 271 Problem Description Given a rooted tree with n vertices, some of the vertices are important. An a

2016 大连网赛---Weak Pair(dfs+树状数组)

题目链接 http://acm.split.hdu.edu.cn/showproblem.php?pid=5877 Problem Description You are given a rooted tree of N nodes, labeled from 1 to N. To the ith node a non-negative value ai is assigned.An ordered pair of nodes (u,v) is said to be weak if  (1) u